From a33cfa922f919790c813774fbd698c886e9aefdb Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Mon, 1 Dec 2025 21:11:17 -0700 Subject: [PATCH 01/19] Add Java Feature Flags setup documentation - Add comprehensive Java SDK onboarding guide - Include installation instructions for Maven and Gradle - Document OpenFeature provider initialization and usage - Add code examples for all flag types (boolean, string, int, double, object) - Document error handling and common error codes - Include best practices and troubleshooting guide - Add Java to feature flags setup page navigation - Document integration with Datadog APM and exposure tracking Related: FFE Server SDK code freeze preparation --- content/en/feature_flags/setup/java.md | 560 ++++++++++++++++++ .../feature_flags/feature_flags_setup.html | 7 + 2 files changed, 567 insertions(+) create mode 100644 content/en/feature_flags/setup/java.md diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md new file mode 100644 index 00000000000..05be08e6c85 --- /dev/null +++ b/content/en/feature_flags/setup/java.md @@ -0,0 +1,560 @@ +--- +title: Java Feature Flags +description: Set up Datadog Feature Flags for Java applications. +further_reading: +- link: "/feature_flags/setup/" + tag: "Documentation" + text: "Feature Flags Setup" +- link: "/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/" + tag: "Documentation" + text: "Java APM and Distributed Tracing" +--- + +{{< callout url="http://datadoghq.com/product-preview/feature-flags/" >}} +Feature Flags are in Preview. Complete the form to request access. +{{< /callout >}} + +
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the configuration section for details.
+ +## Overview + +This page describes how to instrument your Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. + +The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. + +## Prerequisites + +- **Java 11 or higher**: The Feature Flags SDK requires Java 11+ +- **Datadog APM Tracer**: Datadog Feature Flags requires `dd-trace-java` version **1.57.0** or later (currently in development) +- **Datadog Agent**: A running Datadog Agent **7.x or later** with Remote Configuration enabled and EVP Proxy support +- **Datadog API Key**: Required for Remote Configuration + +
Note: Feature Flags support is not yet available in the public release of dd-trace-java. You'll need a development build or wait for the official release.
+ +## Installation + +Feature flagging is included in the Datadog Java APM tracer (`dd-trace-java`). No additional dependencies are required beyond the tracer and the OpenFeature SDK. + +{{< tabs >}} +{{% tab "Gradle" %}} +Add the following dependencies to your `build.gradle`: + +{{< code-block lang="groovy" filename="build.gradle" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation 'com.datadoghq:dd-trace-api:X.X.X' + + // OpenFeature SDK for flag evaluation + implementation 'dev.openfeature:sdk:1.18.2' + + // OpenFeature Provider (packaged separately) + implementation 'com.datadoghq:dd-openfeature:X.X.X' +} +{{< /code-block >}} + +Or with Kotlin DSL (`build.gradle.kts`): + +{{< code-block lang="kotlin" filename="build.gradle.kts" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation("com.datadoghq:dd-trace-api:X.X.X") + + // OpenFeature SDK for flag evaluation + implementation("dev.openfeature:sdk:1.18.2") + + // OpenFeature Provider (packaged separately) + implementation("com.datadoghq:dd-openfeature:X.X.X") +} +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Maven" %}} +Add the following dependencies to your `pom.xml`: + +{{< code-block lang="xml" filename="pom.xml" >}} + + + + com.datadoghq + dd-trace-api + X.X.X + + + + + dev.openfeature + sdk + 1.18.2 + + + + + com.datadoghq + dd-openfeature + X.X.X + + +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +## Configuration + +### Agent Configuration + +Configure your Datadog Agent to enable Remote Configuration: + +{{< code-block lang="yaml" filename="datadog.yaml" >}} +# Enable Remote Configuration +remote_configuration: + enabled: true + +# Set your API key +api_key: +{{< /code-block >}} + +### Application Configuration + +Configure your Java application with the required environment variables or system properties: + +{{< tabs >}} +{{% tab "Environment Variables" %}} +{{< code-block lang="bash" >}} +# Required: Enable Remote Configuration in the tracer +export DD_REMOTE_CONFIG_ENABLED=true + +# Required: Enable experimental feature flagging support +export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + +# Required: Your Datadog API key +export DD_API_KEY= + +# Required: Service name +export DD_SERVICE= + +# Required: Environment (e.g., prod, staging, dev) +export DD_ENV= + +# Optional: Version +export DD_VERSION= + +# Start your application with the tracer +java -javaagent:path/to/dd-java-agent.jar -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} + +{{% tab "System Properties" %}} +{{< code-block lang="bash" >}} +java -javaagent:path/to/dd-java-agent.jar \ + -Ddd.remote.config.enabled=true \ + -Ddd.experimental.flagging.provider.enabled=true \ + -Ddd.api.key= \ + -Ddd.service= \ + -Ddd.env= \ + -Ddd.version= \ + -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the Feature Flagging system will not start.
+ +
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+ +## Initialize the OpenFeature Provider + +Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Client; +import datadog.trace.api.openfeature.Provider; + +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; + +// Initialize the Datadog provider +OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + +try { + // Set provider and wait for initial configuration (recommended) + api.setProviderAndWait(new Provider()); +} catch (ProviderNotReadyError e) { + // Handle gracefully - app will use default flag values + System.err.println("Provider not ready: " + e.getMessage()); + // Continue - client will use defaults until configuration arrives +} + +// Get a client for evaluating flags +Client client = api.getClient(); +{{< /code-block >}} + +
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
+ +
Graceful Degradation: If the provider times out, catch ProviderNotReadyError to allow your application to start with default flag values. This prevents application crashes when Remote Config is unavailable.
+ +### Asynchronous Initialization + +For non-blocking initialization, use `setProvider()` and listen for the `PROVIDER_READY` event: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +OpenFeatureAPI api = OpenFeatureAPI.getInstance(); +Client client = api.getClient(); + +// Listen for provider ready event +client.on(ProviderEvent.PROVIDER_READY, (event) -> { + System.out.println("Feature flags ready!"); +}); + +// Set provider asynchronously +api.setProvider(new Provider()); +{{< /code-block >}} + +## Set the Evaluation Context + +The evaluation context defines the subject (user, device, session) for flag evaluation. It determines which flag variations are returned based on targeting rules. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.MutableContext; + +// Create an evaluation context with a targeting key and attributes +EvaluationContext context = new MutableContext("user-123") + .add("email", "user@example.com") + .add("tier", "premium") + .add("country", "US"); + +// Use the context for flag evaluations (see next section) +{{< /code-block >}} + +
Targeting Key: The targetingKey (e.g., "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
+ +## Evaluate Flags + +Evaluate feature flags using the OpenFeature client. All flag types are supported: boolean, string, integer, double, and object. + +### Boolean Flags + +{{< code-block lang="java" >}} +// Simple boolean evaluation +boolean enabled = client.getBooleanValue("new-checkout-flow", false, context); + +if (enabled) { + // New checkout flow +} else { + // Old checkout flow +} + +// Get detailed evaluation result +import dev.openfeature.sdk.FlagEvaluationDetails; + +FlagEvaluationDetails details = + client.getBooleanDetails("new-checkout-flow", false, context); + +System.out.println("Value: " + details.getValue()); +System.out.println("Variant: " + details.getVariant()); +System.out.println("Reason: " + details.getReason()); +{{< /code-block >}} + +### String Flags + +{{< code-block lang="java" >}} +// Evaluate string flags (e.g., UI themes, API endpoints) +String theme = client.getStringValue("ui-theme", "light", context); + +String apiEndpoint = client.getStringValue( + "payment-api-endpoint", + "https://api.example.com/v1", + context +); +{{< /code-block >}} + +### Number Flags + +{{< code-block lang="java" >}} +// Integer flags (e.g., limits, quotas) +int maxRetries = client.getIntegerValue("max-retries", 3, context); + +// Double flags (e.g., thresholds, rates) +double discountRate = client.getDoubleValue("discount-rate", 0.0, context); +{{< /code-block >}} + +### Object Flags + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.Value; + +// Evaluate object/JSON flags for complex configuration +Value config = client.getObjectValue("feature-config", new Value(), context); + +// Access structured data +if (config.isStructure()) { + Value timeout = config.asStructure().getValue("timeout"); + Value endpoint = config.asStructure().getValue("endpoint"); +} +{{< /code-block >}} + +## Error Handling + +The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ErrorCode; + +// Check evaluation details for errors +FlagEvaluationDetails details = + client.getBooleanDetails("my-flag", false, context); + +if (details.getErrorCode() != null) { + switch (details.getErrorCode()) { + case FLAG_NOT_FOUND: + System.err.println("Flag does not exist"); + break; + case PROVIDER_NOT_READY: + System.err.println("Provider not initialized yet"); + break; + case TARGETING_KEY_MISSING: + System.err.println("Evaluation context missing targeting key"); + break; + case TYPE_MISMATCH: + System.err.println("Flag value type doesn't match requested type"); + break; + default: + System.err.println("Evaluation error: " + details.getErrorMessage()); + } +} +{{< /code-block >}} + +### Common Error Codes + +| Error Code | Description | Resolution | +|------------|-------------|------------| +| `PROVIDER_NOT_READY` | Initial configuration not received | Wait for provider initialization or use `setProviderAndWait()` | +| `FLAG_NOT_FOUND` | Flag doesn't exist in configuration | Check flag key or create flag in Datadog UI | +| `TARGETING_KEY_MISSING` | No targeting key in evaluation context | Provide a targeting key when creating context | +| `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | +| `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | + +## Exposure Tracking + +Flag exposures are automatically tracked and sent to Datadog when: +1. A flag is evaluated successfully +2. The flag's allocation has `doLog=true` configured + +Exposures appear in the Datadog UI and can be used for: +- Analyzing feature adoption +- Correlating feature flags with application performance +- Debugging flag behavior + +No additional code is required - exposures are automatically logged by the Datadog tracer integration. + +## Advanced Configuration + +### Custom Initialization Timeout + +Configure how long the provider waits for initial configuration: + +{{< code-block lang="java" >}} +import datadog.trace.api.openfeature.Provider; +import java.util.concurrent.TimeUnit; + +Provider.Options options = new Provider.Options() + .initTimeout(10, TimeUnit.SECONDS); + +api.setProviderAndWait(new Provider(options)); +{{< /code-block >}} + +### Configuration Change Events + +Listen for configuration updates from Remote Config: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { + System.out.println("Flag configuration updated: " + event.getMessage()); + // Optionally re-evaluate flags or trigger cache refresh +}); +{{< /code-block >}} + +### Multiple Clients + +Use named clients to organize flags by domain or team: + +{{< code-block lang="java" >}} +// All clients use the same provider but can have different contexts +Client checkoutClient = api.getClient("checkout"); +Client analyticsClient = api.getClient("analytics"); + +// Each client can have its own evaluation context +EvaluationContext checkoutContext = new MutableContext("session-abc"); +EvaluationContext analyticsContext = new MutableContext("user-123"); + +boolean newCheckout = checkoutClient.getBooleanValue( + "new-checkout-ui", false, checkoutContext +); + +boolean enhancedAnalytics = analyticsClient.getBooleanValue( + "enhanced-analytics", false, analyticsContext +); +{{< /code-block >}} + +## Best Practices + +### 1. Initialize Early +Initialize the OpenFeature provider as early as possible in your application lifecycle (e.g., in `main()` or application startup) to ensure flags are ready before business logic executes. + +### 2. Use Meaningful Default Values +Always provide sensible default values that maintain safe behavior if flag evaluation fails: + +{{< code-block lang="java" >}} +// Good: Safe default that maintains current behavior +boolean useNewAlgorithm = client.getBooleanValue("new-algorithm", false, context); + +// Good: Conservative default for limits +int rateLimit = client.getIntegerValue("rate-limit", 100, context); +{{< /code-block >}} + +### 3. Create Context Once +Create the evaluation context once per request/user/session and reuse it for all flag evaluations: + +{{< code-block lang="java" >}} +// In a web filter or request handler +EvaluationContext userContext = new MutableContext(userId) + .add("email", user.getEmail()) + .add("plan", user.getPlan()); + +// Reuse context for all flags in this request +boolean featureA = client.getBooleanValue("feature-a", false, userContext); +boolean featureB = client.getBooleanValue("feature-b", false, userContext); +{{< /code-block >}} + +### 4. Handle Initialization Failures +Always handle cases where the provider fails to initialize: + +{{< code-block lang="java" >}} +try { + api.setProviderAndWait(new Provider()); +} catch (Exception e) { + // Log error and continue with defaults + logger.error("Failed to initialize feature flags", e); + // Application will use default values for all flags +} +{{< /code-block >}} + +### 5. Use Consistent Targeting Keys +Use consistent, stable identifiers as targeting keys: +- **Good**: User IDs, session IDs, device IDs +- **Avoid**: Timestamps, random values, frequently changing IDs + +### 6. Monitor Flag Evaluation +Use the detailed evaluation results for logging and debugging: + +{{< code-block lang="java" >}} +FlagEvaluationDetails details = + client.getBooleanDetails("critical-feature", false, context); + +logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", + "critical-feature", + details.getValue(), + details.getVariant(), + details.getReason() +); +{{< /code-block >}} + +## Integration with Datadog APM + +Feature flags automatically integrate with Datadog APM: + +- **Trace Correlation**: Flag evaluations are automatically correlated with APM traces +- **Performance Impact**: Track how feature flags affect application performance +- **Error Tracking**: See which flags were active when errors occurred +- **Exposure Analytics**: Analyze feature adoption in the Datadog UI + +No additional configuration is required - this integration is automatic when using the Datadog tracer. + +## Troubleshooting + +### Provider Not Ready + +**Problem**: `PROVIDER_NOT_READY` errors when evaluating flags + +**Common Causes**: +1. **Experimental flag not enabled**: Feature flagging is disabled by default +2. **Agent not ready**: Application started before Agent was fully initialized +3. **No flags configured**: No flags published to your service/environment combination +4. **Agent Remote Config disabled**: Agent not configured for Remote Configuration + +**Solutions**: +1. **Enable experimental feature**: + ```bash + export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + ``` +2. **Verify Feature Flagging system started** in application logs: + ``` + [dd.trace] Feature Flagging system starting + [dd.trace] Feature Flagging system started + ``` +3. **Ensure Agent is ready** before app starts (use healthchecks in Docker/K8s) +4. **Check EVP Proxy discovered** in logs: + ``` + discovered ... evpProxyEndpoint=evp_proxy/v4/ configEndpoint=v0.7/config + ``` +5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) +6. **Verify flags are published** in Datadog UI to the correct service and environment + +### Feature Flagging System Not Starting + +**Problem**: No "Feature Flagging system starting" messages in logs + +**Cause**: Experimental flag not enabled in tracer + +**Solution**: +Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` + +### EVP Proxy Not Available Error + +**Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" + +**Cause**: Application started before Agent was fully initialized + +**Solutions**: +1. **Add Agent healthcheck** in orchestration (Docker Compose, Kubernetes) +2. **Add startup delay** to application +3. **Retry logic**: Implement retry on provider initialization failure +4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support + +### Flags Not Updating + +**Problem**: Flag configuration changes aren't reflected in the application + +**Solutions**: +1. Check Remote Configuration is enabled on both Agent and application +2. Verify Agent can connect to Datadog backend +3. Check application logs for "No configuration changes" or "Configuration received" +4. Ensure flags are published (not just saved as draft) in the Datadog UI +5. Verify service and environment tags match between app and flag targeting + +### Type Mismatch Errors + +**Problem**: `TYPE_MISMATCH` errors when evaluating flags + +**Solutions**: +1. Verify the flag type in Datadog UI matches the evaluation method +2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` +3. Check flag configuration for correct value types + +### No Exposures in Datadog + +**Problem**: Flag evaluations aren't appearing in Datadog UI + +**Solutions**: +1. Verify the flag's allocation has `doLog=true` configured +2. Check Datadog Agent is receiving exposure events +3. Verify `DD_API_KEY` is correct +4. Check Agent logs for exposure upload errors + +## Further Reading + +{{< partial name="whats-next/whats-next.html" >}} diff --git a/layouts/partials/feature_flags/feature_flags_setup.html b/layouts/partials/feature_flags/feature_flags_setup.html index c4f9b4cf6de..91879f94dda 100644 --- a/layouts/partials/feature_flags/feature_flags_setup.html +++ b/layouts/partials/feature_flags/feature_flags_setup.html @@ -30,6 +30,13 @@ + From bcf5d5c74c20b7657c0363624d07622ca2eae877 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 11:20:42 -0700 Subject: [PATCH 02/19] fix(docs): Add missing bootstrap JAR and improve Java Feature Flags documentation Critical fixes: - Add missing dd-java-agent-feature-flagging-bootstrap dependency to all examples - Add Building from Source section with detailed instructions - Explain bootstrap module purpose (classloader communication) - Add troubleshooting for ClassNotFoundException Improvements to match Java APM documentation patterns: - Add application server configuration tabs (Spring Boot, Tomcat, JBoss, Jetty, Docker) - Update all code examples to use SLF4J logger instead of System.out/err - Add Compatibility requirements section - Add Getting started section - Update initialization example to show complete application structure Code quality: - Use logger.info/warn/error throughout with parameterized messages - Show proper exception handling with logger - Match actual implementation in ffe-dogfooding repo - Include named client usage ("my-app") The documentation now accurately reflects the required dependencies, follows established Datadog docs patterns, and provides complete setup instructions for the development build. --- content/en/feature_flags/setup/java.md | 237 +++++++++++++++++++++---- 1 file changed, 203 insertions(+), 34 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 05be08e6c85..3f5a7da0d5e 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -22,18 +22,25 @@ This page describes how to instrument your Java application with the Datadog Fea The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. -## Prerequisites +## Compatibility requirements -- **Java 11 or higher**: The Feature Flags SDK requires Java 11+ -- **Datadog APM Tracer**: Datadog Feature Flags requires `dd-trace-java` version **1.57.0** or later (currently in development) -- **Datadog Agent**: A running Datadog Agent **7.x or later** with Remote Configuration enabled and EVP Proxy support +The Datadog Feature Flags SDK for Java requires: +- **Java 11 or higher** +- **Datadog Java APM Tracer**: Version **1.57.0** or later +- **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled - **Datadog API Key**: Required for Remote Configuration -
Note: Feature Flags support is not yet available in the public release of dd-trace-java. You'll need a development build or wait for the official release.
+For a full list of Datadog's Java version and framework support, read [Compatibility Requirements](/tracing/trace_collection/compatibility/java/). + +## Getting started + +Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). + +
Development Build Required: Feature Flags support is currently available in development builds of dd-trace-java version 1.57.0. It will be included in an upcoming public release. See the Building from Source section for instructions.
## Installation -Feature flagging is included in the Datadog Java APM tracer (`dd-trace-java`). No additional dependencies are required beyond the tracer and the OpenFeature SDK. +Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. {{< tabs >}} {{% tab "Gradle" %}} @@ -47,8 +54,11 @@ dependencies { // OpenFeature SDK for flag evaluation implementation 'dev.openfeature:sdk:1.18.2' - // OpenFeature Provider (packaged separately) + // Datadog OpenFeature Provider implementation 'com.datadoghq:dd-openfeature:X.X.X' + + // Datadog Feature Flagging Bootstrap (required) + implementation 'com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X' } {{< /code-block >}} @@ -62,8 +72,11 @@ dependencies { // OpenFeature SDK for flag evaluation implementation("dev.openfeature:sdk:1.18.2") - // OpenFeature Provider (packaged separately) + // Datadog OpenFeature Provider implementation("com.datadoghq:dd-openfeature:X.X.X") + + // Datadog Feature Flagging Bootstrap (required) + implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") } {{< /code-block >}} {{% /tab %}} @@ -87,17 +100,71 @@ Add the following dependencies to your `pom.xml`: 1.18.2 - + com.datadoghq dd-openfeature X.X.X + + + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + {{< /code-block >}} {{% /tab %}} {{< /tabs >}} +### Building from Source + +
Temporary Requirement: Until the Feature Flags artifacts are published to Maven Central, you'll need to build them locally from the dd-trace-java repository.
+ +1. Clone the `dd-trace-java` repository: + +{{< code-block lang="bash" >}} +git clone https://github.com/DataDog/dd-trace-java.git +cd dd-trace-java +{{< /code-block >}} + +2. Build the Feature Flagging modules: + +{{< code-block lang="bash" >}} +./gradlew :products:feature-flagging:api:jar :products:feature-flagging:bootstrap:jar +{{< /code-block >}} + +3. The built JARs will be located at: + - `products/feature-flagging/api/build/libs/dd-openfeature-X.X.X-SNAPSHOT.jar` + - `products/feature-flagging/bootstrap/build/libs/bootstrap-X.X.X-SNAPSHOT.jar` + +4. Use these JARs with system scope in your `pom.xml` or copy them to your project's `libs/` directory: + +{{< code-block lang="xml" filename="pom.xml" >}} + + + + com.datadoghq + dd-openfeature + 1.57.0-SNAPSHOT + system + ${project.basedir}/libs/dd-openfeature-1.57.0-SNAPSHOT.jar + + + + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + 1.57.0-SNAPSHOT + system + ${project.basedir}/libs/bootstrap-1.57.0-SNAPSHOT.jar + + +{{< /code-block >}} + +
Note: The bootstrap JAR contains shared interfaces between the tracer and OpenFeature provider. It enables communication across Java classloader boundaries. Both JARs are required for feature flags to work.
+ ## Configuration ### Agent Configuration @@ -161,6 +228,72 @@ java -javaagent:path/to/dd-java-agent.jar \
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+### Add the Java Tracer to the JVM + +Use the documentation for your application server to figure out the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: + +{{< tabs >}} +{{% tab "Spring Boot" %}} + +If your app is called `my_app.jar`, create a `my_app.conf`, containing: + +```text +JAVA_OPTS=-javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true +``` + +For more information, see the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-script-customization-when-it-runs). + +{{% /tab %}} +{{% tab "Tomcat" %}} + +Open your Tomcat startup script file, for example `setenv.sh`, and add: + +```text +CATALINA_OPTS="$CATALINA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "JBoss" %}} + +Add the following line to the end of `standalone.conf`: + +```text +JAVA_OPTS="$JAVA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "Jetty" %}} + +Add the following to your `start.ini`: + +```text +--exec +-javaagent:/path/to/dd-java-agent.jar +-Ddd.remote.config.enabled=true +-Ddd.experimental.flagging.provider.enabled=true +``` + +{{% /tab %}} +{{% tab "Docker" %}} + +Update your Dockerfile to download and use the tracer: + +```dockerfile +FROM openjdk:17-jre + +# Download the Datadog Java tracer +ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar + +# Copy your application +COPY myapp.jar . + +# Run with feature flags enabled +CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", "-Ddd.experimental.flagging.provider.enabled=true", "-jar", "myapp.jar"] +``` + +{{% /tab %}} +{{< /tabs >}} + ## Initialize the OpenFeature Provider Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. @@ -169,23 +302,37 @@ Initialize the Datadog OpenFeature provider in your application startup code. Th import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Client; import datadog.trace.api.openfeature.Provider; - import dev.openfeature.sdk.exceptions.ProviderNotReadyError; - -// Initialize the Datadog provider -OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - -try { - // Set provider and wait for initial configuration (recommended) - api.setProviderAndWait(new Provider()); -} catch (ProviderNotReadyError e) { - // Handle gracefully - app will use default flag values - System.err.println("Provider not ready: " + e.getMessage()); - // Continue - client will use defaults until configuration arrives +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + private static final Logger logger = LoggerFactory.getLogger(App.class); + private static Client client; + + public static void main(String[] args) throws Exception { + // Initialize the Datadog provider + logger.info("Initializing Datadog OpenFeature Provider..."); + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + + try { + // Set provider and wait for initial configuration (recommended) + api.setProviderAndWait(new Provider()); + client = api.getClient("my-app"); + logger.info("OpenFeature provider initialized successfully"); + } catch (ProviderNotReadyError e) { + // Handle gracefully - app will use default flag values + logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); + client = api.getClient("my-app"); + logger.info("App will use default flag values until provider is ready"); + } catch (Exception e) { + logger.error("Failed to initialize OpenFeature provider", e); + throw e; + } + + // Your application code here + } } - -// Get a client for evaluating flags -Client client = api.getClient(); {{< /code-block >}}
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
@@ -204,7 +351,7 @@ Client client = api.getClient(); // Listen for provider ready event client.on(ProviderEvent.PROVIDER_READY, (event) -> { - System.out.println("Feature flags ready!"); + logger.info("Feature flags ready!"); }); // Set provider asynchronously @@ -252,9 +399,9 @@ import dev.openfeature.sdk.FlagEvaluationDetails; FlagEvaluationDetails details = client.getBooleanDetails("new-checkout-flow", false, context); -System.out.println("Value: " + details.getValue()); -System.out.println("Variant: " + details.getVariant()); -System.out.println("Reason: " + details.getReason()); +logger.info("Value: {}", details.getValue()); +logger.info("Variant: {}", details.getVariant()); +logger.info("Reason: {}", details.getReason()); {{< /code-block >}} ### String Flags @@ -309,19 +456,19 @@ FlagEvaluationDetails details = if (details.getErrorCode() != null) { switch (details.getErrorCode()) { case FLAG_NOT_FOUND: - System.err.println("Flag does not exist"); + logger.warn("Flag does not exist: {}", "my-flag"); break; case PROVIDER_NOT_READY: - System.err.println("Provider not initialized yet"); + logger.warn("Provider not initialized yet"); break; case TARGETING_KEY_MISSING: - System.err.println("Evaluation context missing targeting key"); + logger.warn("Evaluation context missing targeting key"); break; case TYPE_MISMATCH: - System.err.println("Flag value type doesn't match requested type"); + logger.error("Flag value type doesn't match requested type"); break; default: - System.err.println("Evaluation error: " + details.getErrorMessage()); + logger.error("Evaluation error for flag: {}", "my-flag", details.getErrorCode()); } } {{< /code-block >}} @@ -373,7 +520,7 @@ Listen for configuration updates from Remote Config: import dev.openfeature.sdk.ProviderEvent; client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { - System.out.println("Flag configuration updated: " + event.getMessage()); + logger.info("Flag configuration updated: {}", event.getMessage()); // Optionally re-evaluate flags or trigger cache refresh }); {{< /code-block >}} @@ -504,6 +651,28 @@ No additional configuration is required - this integration is automatic when usi 5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) 6. **Verify flags are published** in Datadog UI to the correct service and environment +### ClassNotFoundException or NoClassDefFoundError + +**Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` + +**Cause**: Missing the bootstrap JAR dependency + +**Solutions**: +1. **Add the bootstrap JAR** to your dependencies: + ```xml + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + + ``` +2. **Verify both JARs are present** if building locally: + - `dd-openfeature-X.X.X.jar` (the provider) + - `bootstrap-X.X.X.jar` (the bootstrap module) +3. **Check the classpath** includes both JARs in your runtime configuration + +
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
+ ### Feature Flagging System Not Starting **Problem**: No "Feature Flagging system starting" messages in logs From f7f5b82586b024509bcda9c21c6bde5cc685e003 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:08:20 -0700 Subject: [PATCH 03/19] docs(java): Improve documentation based on review feedback Changes: - Add cross-linking to Configuration section from early warning - Add OpenFeature SDK to compatibility requirements (it is required) - Remove all local build instructions (only reference published X.X.X versions) - Add skip guidance for users with existing agent/remote-config setup - Clarify ProviderNotReadyError is OpenFeature exception (optional handling) - Update exception handling to be optional based on availability requirements - Note that not catching exception may prevent application startup Improvements: - Users with existing APM can skip to provider initialization - Users with existing remote-config can skip agent configuration - Clear guidance on when exception handling is optional vs required - Removed all references to building from source --- content/en/feature_flags/setup/java.md | 81 +++++++------------------- 1 file changed, 20 insertions(+), 61 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 3f5a7da0d5e..60bdb8eee5a 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -14,7 +14,7 @@ further_reading: Feature Flags are in Preview. Complete the form to request access. {{< /callout >}} -
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the configuration section for details.
+
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
## Overview @@ -22,11 +22,14 @@ This page describes how to instrument your Java application with the Datadog Fea The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. +
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
+ ## Compatibility requirements The Datadog Feature Flags SDK for Java requires: - **Java 11 or higher** - **Datadog Java APM Tracer**: Version **1.57.0** or later +- **OpenFeature SDK**: Version **1.18.2** or later - **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled - **Datadog API Key**: Required for Remote Configuration @@ -36,8 +39,6 @@ For a full list of Datadog's Java version and framework support, read [Compatibi Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). -
Development Build Required: Feature Flags support is currently available in development builds of dd-trace-java version 1.57.0. It will be included in an upcoming public release. See the Building from Source section for instructions.
- ## Installation Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. @@ -118,55 +119,12 @@ Add the following dependencies to your `pom.xml`: {{% /tab %}} {{< /tabs >}} -### Building from Source - -
Temporary Requirement: Until the Feature Flags artifacts are published to Maven Central, you'll need to build them locally from the dd-trace-java repository.
- -1. Clone the `dd-trace-java` repository: - -{{< code-block lang="bash" >}} -git clone https://github.com/DataDog/dd-trace-java.git -cd dd-trace-java -{{< /code-block >}} - -2. Build the Feature Flagging modules: - -{{< code-block lang="bash" >}} -./gradlew :products:feature-flagging:api:jar :products:feature-flagging:bootstrap:jar -{{< /code-block >}} - -3. The built JARs will be located at: - - `products/feature-flagging/api/build/libs/dd-openfeature-X.X.X-SNAPSHOT.jar` - - `products/feature-flagging/bootstrap/build/libs/bootstrap-X.X.X-SNAPSHOT.jar` - -4. Use these JARs with system scope in your `pom.xml` or copy them to your project's `libs/` directory: - -{{< code-block lang="xml" filename="pom.xml" >}} - - - - com.datadoghq - dd-openfeature - 1.57.0-SNAPSHOT - system - ${project.basedir}/libs/dd-openfeature-1.57.0-SNAPSHOT.jar - - - - - com.datadoghq - dd-java-agent-feature-flagging-bootstrap - 1.57.0-SNAPSHOT - system - ${project.basedir}/libs/bootstrap-1.57.0-SNAPSHOT.jar - - -{{< /code-block >}} - -
Note: The bootstrap JAR contains shared interfaces between the tracer and OpenFeature provider. It enables communication across Java classloader boundaries. Both JARs are required for feature flags to work.
+
What is the bootstrap JAR? The dd-java-agent-feature-flagging-bootstrap JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work.
## Configuration +
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
+ ### Agent Configuration Configure your Datadog Agent to enable Remote Configuration: @@ -182,6 +140,8 @@ api_key: ### Application Configuration +
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
+ Configure your Java application with the required environment variables or system properties: {{< tabs >}} @@ -321,13 +281,10 @@ public class App { client = api.getClient("my-app"); logger.info("OpenFeature provider initialized successfully"); } catch (ProviderNotReadyError e) { - // Handle gracefully - app will use default flag values + // Optional: Handle gracefully - app will use default flag values logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); client = api.getClient("my-app"); logger.info("App will use default flag values until provider is ready"); - } catch (Exception e) { - logger.error("Failed to initialize OpenFeature provider", e); - throw e; } // Your application code here @@ -337,7 +294,7 @@ public class App {
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
-
Graceful Degradation: If the provider times out, catch ProviderNotReadyError to allow your application to start with default flag values. This prevents application crashes when Remote Config is unavailable.
+
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows your application to start with default flag values if Remote Config is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
### Asynchronous Initialization @@ -577,19 +534,21 @@ boolean featureA = client.getBooleanValue("feature-a", false, userContext); boolean featureB = client.getBooleanValue("feature-b", false, userContext); {{< /code-block >}} -### 4. Handle Initialization Failures -Always handle cases where the provider fails to initialize: +### 4. Handle Initialization Failures (Optional) +Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} try { api.setProviderAndWait(new Provider()); -} catch (Exception e) { +} catch (ProviderNotReadyError e) { // Log error and continue with defaults - logger.error("Failed to initialize feature flags", e); + logger.warn("Feature flags not ready, using defaults", e); // Application will use default values for all flags } {{< /code-block >}} +If feature flags are critical for your application to function, let the exception propagate to prevent startup. + ### 5. Use Consistent Targeting Keys Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs @@ -666,9 +625,9 @@ No additional configuration is required - this integration is automatic when usi X.X.X ``` -2. **Verify both JARs are present** if building locally: - - `dd-openfeature-X.X.X.jar` (the provider) - - `bootstrap-X.X.X.jar` (the bootstrap module) +2. **Verify both dependencies are included** in your build: + - `dd-openfeature` (the OpenFeature provider) + - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) 3. **Check the classpath** includes both JARs in your runtime configuration
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
From e6ecc5b0669988272521da1e007cf2237f4c09db Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:11:21 -0700 Subject: [PATCH 04/19] docs(java): Add event state watching and clarify provider instance sharing Changes: - Add PROVIDER_ERROR and PROVIDER_STALE event listeners to async init example - Note that PROVIDER_CONFIGURATION_CHANGED is optional (depends on provider support) - Update multiple clients section: "organize context and flags" (not just flags) - Clarify that Provider instance is shared globally (client names are organizational only) The Provider constructor doesn't take a name parameter - it's a single shared instance. Named clients are just for organizing your application code, not separate providers. --- content/en/feature_flags/setup/java.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 60bdb8eee5a..0da13a917aa 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -298,7 +298,7 @@ public class App { ### Asynchronous Initialization -For non-blocking initialization, use `setProvider()` and listen for the `PROVIDER_READY` event: +For non-blocking initialization, use `setProvider()` and listen for provider events: {{< code-block lang="java" >}} import dev.openfeature.sdk.ProviderEvent; @@ -306,11 +306,19 @@ import dev.openfeature.sdk.ProviderEvent; OpenFeatureAPI api = OpenFeatureAPI.getInstance(); Client client = api.getClient(); -// Listen for provider ready event +// Listen for provider state changes client.on(ProviderEvent.PROVIDER_READY, (event) -> { logger.info("Feature flags ready!"); }); +client.on(ProviderEvent.PROVIDER_ERROR, (event) -> { + logger.error("Provider error: {}", event.getMessage()); +}); + +client.on(ProviderEvent.PROVIDER_STALE, (event) -> { + logger.warn("Provider configuration is stale"); +}); + // Set provider asynchronously api.setProvider(new Provider()); {{< /code-block >}} @@ -482,12 +490,14 @@ client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { }); {{< /code-block >}} +
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
+ ### Multiple Clients -Use named clients to organize flags by domain or team: +Use named clients to organize context and flags by domain or team: {{< code-block lang="java" >}} -// All clients use the same provider but can have different contexts +// Named clients share the same provider instance but can have different contexts Client checkoutClient = api.getClient("checkout"); Client analyticsClient = api.getClient("analytics"); @@ -504,6 +514,8 @@ boolean enhancedAnalytics = analyticsClient.getBooleanValue( ); {{< /code-block >}} +
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
+ ## Best Practices ### 1. Initialize Early From 3013f11ab6bffdf98b7a30eed7d89dfe9a5a450f Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:25:14 -0700 Subject: [PATCH 05/19] docs(java): Align flag keys and attributes with mobile SDK conventions Changes: - Update all flag keys to use dot notation (matching Android/iOS): * new-checkout-flow -> checkout.new * ui-theme -> ui.theme * payment-api-endpoint -> payment.api.endpoint * max-retries -> retries.max * discount-rate -> pricing.discount.rate * feature-config -> ui.config * All other flags updated to dot notation - Standardize attribute to use "tier" consistently (not "plan") - Remove "country" attribute from basic example to match mobile simplicity This creates consistency across all SDK documentation and makes it easier for users to migrate between platforms or reference examples. --- content/en/feature_flags/setup/java.md | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 0da13a917aa..13f89596ad3 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -334,8 +334,7 @@ import dev.openfeature.sdk.MutableContext; // Create an evaluation context with a targeting key and attributes EvaluationContext context = new MutableContext("user-123") .add("email", "user@example.com") - .add("tier", "premium") - .add("country", "US"); + .add("tier", "premium"); // Use the context for flag evaluations (see next section) {{< /code-block >}} @@ -350,7 +349,7 @@ Evaluate feature flags using the OpenFeature client. All flag types are supporte {{< code-block lang="java" >}} // Simple boolean evaluation -boolean enabled = client.getBooleanValue("new-checkout-flow", false, context); +boolean enabled = client.getBooleanValue("checkout.new", false, context); if (enabled) { // New checkout flow @@ -362,7 +361,7 @@ if (enabled) { import dev.openfeature.sdk.FlagEvaluationDetails; FlagEvaluationDetails details = - client.getBooleanDetails("new-checkout-flow", false, context); + client.getBooleanDetails("checkout.new", false, context); logger.info("Value: {}", details.getValue()); logger.info("Variant: {}", details.getVariant()); @@ -373,10 +372,10 @@ logger.info("Reason: {}", details.getReason()); {{< code-block lang="java" >}} // Evaluate string flags (e.g., UI themes, API endpoints) -String theme = client.getStringValue("ui-theme", "light", context); +String theme = client.getStringValue("ui.theme", "light", context); String apiEndpoint = client.getStringValue( - "payment-api-endpoint", + "payment.api.endpoint", "https://api.example.com/v1", context ); @@ -386,10 +385,10 @@ String apiEndpoint = client.getStringValue( {{< code-block lang="java" >}} // Integer flags (e.g., limits, quotas) -int maxRetries = client.getIntegerValue("max-retries", 3, context); +int maxRetries = client.getIntegerValue("retries.max", 3, context); // Double flags (e.g., thresholds, rates) -double discountRate = client.getDoubleValue("discount-rate", 0.0, context); +double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); {{< /code-block >}} ### Object Flags @@ -398,7 +397,7 @@ double discountRate = client.getDoubleValue("discount-rate", 0.0, context); import dev.openfeature.sdk.Value; // Evaluate object/JSON flags for complex configuration -Value config = client.getObjectValue("feature-config", new Value(), context); +Value config = client.getObjectValue("ui.config", new Value(), context); // Access structured data if (config.isStructure()) { @@ -416,12 +415,12 @@ import dev.openfeature.sdk.ErrorCode; // Check evaluation details for errors FlagEvaluationDetails details = - client.getBooleanDetails("my-flag", false, context); + client.getBooleanDetails("checkout.new", false, context); if (details.getErrorCode() != null) { switch (details.getErrorCode()) { case FLAG_NOT_FOUND: - logger.warn("Flag does not exist: {}", "my-flag"); + logger.warn("Flag does not exist: {}", "checkout.new"); break; case PROVIDER_NOT_READY: logger.warn("Provider not initialized yet"); @@ -433,7 +432,7 @@ if (details.getErrorCode() != null) { logger.error("Flag value type doesn't match requested type"); break; default: - logger.error("Evaluation error for flag: {}", "my-flag", details.getErrorCode()); + logger.error("Evaluation error for flag: {}", "checkout.new", details.getErrorCode()); } } {{< /code-block >}} @@ -506,11 +505,11 @@ EvaluationContext checkoutContext = new MutableContext("session-abc"); EvaluationContext analyticsContext = new MutableContext("user-123"); boolean newCheckout = checkoutClient.getBooleanValue( - "new-checkout-ui", false, checkoutContext + "checkout.ui.new", false, checkoutContext ); boolean enhancedAnalytics = analyticsClient.getBooleanValue( - "enhanced-analytics", false, analyticsContext + "analytics.enhanced", false, analyticsContext ); {{< /code-block >}} @@ -526,10 +525,10 @@ Always provide sensible default values that maintain safe behavior if flag evalu {{< code-block lang="java" >}} // Good: Safe default that maintains current behavior -boolean useNewAlgorithm = client.getBooleanValue("new-algorithm", false, context); +boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context); // Good: Conservative default for limits -int rateLimit = client.getIntegerValue("rate-limit", 100, context); +int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} ### 3. Create Context Once @@ -539,11 +538,11 @@ Create the evaluation context once per request/user/session and reuse it for all // In a web filter or request handler EvaluationContext userContext = new MutableContext(userId) .add("email", user.getEmail()) - .add("plan", user.getPlan()); + .add("tier", user.getTier()); // Reuse context for all flags in this request -boolean featureA = client.getBooleanValue("feature-a", false, userContext); -boolean featureB = client.getBooleanValue("feature-b", false, userContext); +boolean featureA = client.getBooleanValue("feature.a", false, userContext); +boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} ### 4. Handle Initialization Failures (Optional) @@ -571,10 +570,10 @@ Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} FlagEvaluationDetails details = - client.getBooleanDetails("critical-feature", false, context); + client.getBooleanDetails("feature.critical", false, context); logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - "critical-feature", + "feature.critical", details.getValue(), details.getVariant(), details.getReason() From 94fc52cf323b914e6bb17855fe492c5326b23625 Mon Sep 17 00:00:00 2001 From: Joe Peeples Date: Fri, 5 Dec 2025 15:11:17 -0500 Subject: [PATCH 06/19] fix tile image, add page to side nav --- config/_default/menus/main.en.yaml | 5 +++++ layouts/partials/feature_flags/feature_flags_setup.html | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/_default/menus/main.en.yaml b/config/_default/menus/main.en.yaml index 87218bf55e6..29e3213cf70 100644 --- a/config/_default/menus/main.en.yaml +++ b/config/_default/menus/main.en.yaml @@ -5577,6 +5577,11 @@ menu: parent: feature_flags_setup identifier: feature_flags_setup_ios weight: 102 + - name: Java + url: feature_flags/setup/java + parent: feature_flags_setup + identifier: feature_flags_setup_java + weight: 103 - name: MCP Server url: feature_flags/feature_flag_mcp_server parent: feature_flags diff --git a/layouts/partials/feature_flags/feature_flags_setup.html b/layouts/partials/feature_flags/feature_flags_setup.html index 91879f94dda..a9a4285f49d 100644 --- a/layouts/partials/feature_flags/feature_flags_setup.html +++ b/layouts/partials/feature_flags/feature_flags_setup.html @@ -33,7 +33,7 @@ From 4a9139acd5025813e86cd47db2880073471e18ac Mon Sep 17 00:00:00 2001 From: Joe Peeples Date: Mon, 8 Dec 2025 11:23:35 -0500 Subject: [PATCH 07/19] headings: sentence-case capitalization --- content/en/feature_flags/setup/java.md | 64 +++++++++++++------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 13f89596ad3..123e85892ee 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -125,7 +125,7 @@ Add the following dependencies to your `pom.xml`:
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
-### Agent Configuration +### Agent configuration Configure your Datadog Agent to enable Remote Configuration: @@ -138,7 +138,7 @@ remote_configuration: api_key: {{< /code-block >}} -### Application Configuration +### Application configuration
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
@@ -188,7 +188,7 @@ java -javaagent:path/to/dd-java-agent.jar \
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
-### Add the Java Tracer to the JVM +### Add the Java tracer to the JVM Use the documentation for your application server to figure out the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: @@ -254,7 +254,7 @@ CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", {{% /tab %}} {{< /tabs >}} -## Initialize the OpenFeature Provider +## Initialize the OpenFeature provider Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. @@ -296,7 +296,7 @@ public class App {
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows your application to start with default flag values if Remote Config is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
-### Asynchronous Initialization +### Asynchronous initialization For non-blocking initialization, use `setProvider()` and listen for provider events: @@ -323,7 +323,7 @@ client.on(ProviderEvent.PROVIDER_STALE, (event) -> { api.setProvider(new Provider()); {{< /code-block >}} -## Set the Evaluation Context +## Set the evaluation context The evaluation context defines the subject (user, device, session) for flag evaluation. It determines which flag variations are returned based on targeting rules. @@ -341,11 +341,11 @@ EvaluationContext context = new MutableContext("user-123")
Targeting Key: The targetingKey (e.g., "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
-## Evaluate Flags +## Evaluate flags Evaluate feature flags using the OpenFeature client. All flag types are supported: boolean, string, integer, double, and object. -### Boolean Flags +### Boolean flags {{< code-block lang="java" >}} // Simple boolean evaluation @@ -368,7 +368,7 @@ logger.info("Variant: {}", details.getVariant()); logger.info("Reason: {}", details.getReason()); {{< /code-block >}} -### String Flags +### String flags {{< code-block lang="java" >}} // Evaluate string flags (e.g., UI themes, API endpoints) @@ -381,7 +381,7 @@ String apiEndpoint = client.getStringValue( ); {{< /code-block >}} -### Number Flags +### Number flags {{< code-block lang="java" >}} // Integer flags (e.g., limits, quotas) @@ -391,7 +391,7 @@ int maxRetries = client.getIntegerValue("retries.max", 3, context); double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); {{< /code-block >}} -### Object Flags +### Object flags {{< code-block lang="java" >}} import dev.openfeature.sdk.Value; @@ -406,7 +406,7 @@ if (config.isStructure()) { } {{< /code-block >}} -## Error Handling +## Error handling The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. @@ -437,7 +437,7 @@ if (details.getErrorCode() != null) { } {{< /code-block >}} -### Common Error Codes +### Common error codes | Error Code | Description | Resolution | |------------|-------------|------------| @@ -447,7 +447,7 @@ if (details.getErrorCode() != null) { | `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | | `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | -## Exposure Tracking +## Exposure tracking Flag exposures are automatically tracked and sent to Datadog when: 1. A flag is evaluated successfully @@ -460,9 +460,9 @@ Exposures appear in the Datadog UI and can be used for: No additional code is required - exposures are automatically logged by the Datadog tracer integration. -## Advanced Configuration +## Advanced configuration -### Custom Initialization Timeout +### Custom initialization timeout Configure how long the provider waits for initial configuration: @@ -476,7 +476,7 @@ Provider.Options options = new Provider.Options() api.setProviderAndWait(new Provider(options)); {{< /code-block >}} -### Configuration Change Events +### Configuration change events Listen for configuration updates from Remote Config: @@ -491,7 +491,7 @@ client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> {
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
-### Multiple Clients +### Multiple clients Use named clients to organize context and flags by domain or team: @@ -515,12 +515,12 @@ boolean enhancedAnalytics = analyticsClient.getBooleanValue(
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
-## Best Practices +## Best practices -### 1. Initialize Early +### 1. Initialize early Initialize the OpenFeature provider as early as possible in your application lifecycle (e.g., in `main()` or application startup) to ensure flags are ready before business logic executes. -### 2. Use Meaningful Default Values +### 2. Use meaningful default values Always provide sensible default values that maintain safe behavior if flag evaluation fails: {{< code-block lang="java" >}} @@ -531,7 +531,7 @@ boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} -### 3. Create Context Once +### 3. Create context once Create the evaluation context once per request/user/session and reuse it for all flag evaluations: {{< code-block lang="java" >}} @@ -545,7 +545,7 @@ boolean featureA = client.getBooleanValue("feature.a", false, userContext); boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} -### 4. Handle Initialization Failures (Optional) +### 4. Handle initialization failures (optional) Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} @@ -560,12 +560,12 @@ try { If feature flags are critical for your application to function, let the exception propagate to prevent startup. -### 5. Use Consistent Targeting Keys +### 5. Use consistent targeting keys Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs - **Avoid**: Timestamps, random values, frequently changing IDs -### 6. Monitor Flag Evaluation +### 6. Monitor flag evaluation Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} @@ -593,7 +593,7 @@ No additional configuration is required - this integration is automatic when usi ## Troubleshooting -### Provider Not Ready +### Provider not ready **Problem**: `PROVIDER_NOT_READY` errors when evaluating flags @@ -643,7 +643,7 @@ No additional configuration is required - this integration is automatic when usi
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
-### Feature Flagging System Not Starting +### Feature flagging system not starting **Problem**: No "Feature Flagging system starting" messages in logs @@ -652,7 +652,7 @@ No additional configuration is required - this integration is automatic when usi **Solution**: Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` -### EVP Proxy Not Available Error +### EVP proxy not available error **Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" @@ -664,7 +664,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. **Retry logic**: Implement retry on provider initialization failure 4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support -### Flags Not Updating +### Flags not updating **Problem**: Flag configuration changes aren't reflected in the application @@ -675,7 +675,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 4. Ensure flags are published (not just saved as draft) in the Datadog UI 5. Verify service and environment tags match between app and flag targeting -### Type Mismatch Errors +### Type mismatch errors **Problem**: `TYPE_MISMATCH` errors when evaluating flags @@ -684,7 +684,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` 3. Check flag configuration for correct value types -### No Exposures in Datadog +### No exposures in Datadog **Problem**: Flag evaluations aren't appearing in Datadog UI @@ -694,6 +694,6 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. Verify `DD_API_KEY` is correct 4. Check Agent logs for exposure upload errors -## Further Reading +## Further reading {{< partial name="whats-next/whats-next.html" >}} From 57defab945b356878f6b0fae40398638c9e5d036 Mon Sep 17 00:00:00 2001 From: Joe Peeples Date: Mon, 8 Dec 2025 14:53:50 -0500 Subject: [PATCH 08/19] style linter, general style edits --- content/en/feature_flags/setup/java.md | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 123e85892ee..50a4d8af39e 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -14,13 +14,13 @@ further_reading: Feature Flags are in Preview. Complete the form to request access. {{< /callout >}} -
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
+
Experimental Feature: Java Feature Flags support is experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
## Overview -This page describes how to instrument your Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. +This page describes how to instrument a Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. -The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. +The Java SDK integrates feature flags directly into the Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility.
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
@@ -41,7 +41,7 @@ Before you begin, make sure you've already [installed and configured the Agent]( ## Installation -Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. +Feature flagging is integrated into the Datadog Java APM tracer. You need the tracer JAR and the OpenFeature SDK dependencies. {{< tabs >}} {{% tab "Gradle" %}} @@ -184,13 +184,13 @@ java -javaagent:path/to/dd-java-agent.jar \ {{% /tab %}} {{< /tabs >}} -
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the Feature Flagging system will not start.
+
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start.
-
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+
Note: The Datadog feature flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
### Add the Java tracer to the JVM -Use the documentation for your application server to figure out the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: +Use the documentation for your application server to determine the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: {{< tabs >}} {{% tab "Spring Boot" %}} @@ -282,7 +282,7 @@ public class App { logger.info("OpenFeature provider initialized successfully"); } catch (ProviderNotReadyError e) { // Optional: Handle gracefully - app will use default flag values - logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); + logger.warn("Provider not ready (no tracer/configuration available), continuing with defaults", e); client = api.getClient("my-app"); logger.info("App will use default flag values until provider is ready"); } @@ -292,9 +292,9 @@ public class App { } {{< /code-block >}} -
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
+
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Configuration. This ensures flags are ready before the application starts serving traffic. The default timeout is 30 seconds.
-
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows your application to start with default flag values if Remote Config is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
+
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows the application to start with default flag values if Remote Configuration is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
### Asynchronous initialization @@ -339,16 +339,16 @@ EvaluationContext context = new MutableContext("user-123") // Use the context for flag evaluations (see next section) {{< /code-block >}} -
Targeting Key: The targetingKey (e.g., "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
+
Targeting Key: The targetingKey (for example, "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
## Evaluate flags -Evaluate feature flags using the OpenFeature client. All flag types are supported: boolean, string, integer, double, and object. +Evaluate feature flags using the OpenFeature client. All flag types are supported: Boolean, string, integer, double, and object. ### Boolean flags {{< code-block lang="java" >}} -// Simple boolean evaluation +// Simple Boolean evaluation boolean enabled = client.getBooleanValue("checkout.new", false, context); if (enabled) { @@ -478,7 +478,7 @@ api.setProviderAndWait(new Provider(options)); ### Configuration change events -Listen for configuration updates from Remote Config: +Listen for configuration updates from Remote Configuration: {{< code-block lang="java" >}} import dev.openfeature.sdk.ProviderEvent; @@ -518,7 +518,7 @@ boolean enhancedAnalytics = analyticsClient.getBooleanValue( ## Best practices ### 1. Initialize early -Initialize the OpenFeature provider as early as possible in your application lifecycle (e.g., in `main()` or application startup) to ensure flags are ready before business logic executes. +Initialize the OpenFeature provider as early as possible in your application lifecycle (for example, in `main()` or application startup). This ensures flags are ready before business logic executes. ### 2. Use meaningful default values Always provide sensible default values that maintain safe behavior if flag evaluation fails: @@ -601,24 +601,24 @@ No additional configuration is required - this integration is automatic when usi 1. **Experimental flag not enabled**: Feature flagging is disabled by default 2. **Agent not ready**: Application started before Agent was fully initialized 3. **No flags configured**: No flags published to your service/environment combination -4. **Agent Remote Config disabled**: Agent not configured for Remote Configuration +4. **Agent Remote Configuration disabled**: Agent not configured for Remote Configuration **Solutions**: 1. **Enable experimental feature**: ```bash export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true ``` -2. **Verify Feature Flagging system started** in application logs: +2. **Verify feature flagging system started** in application logs: ``` [dd.trace] Feature Flagging system starting [dd.trace] Feature Flagging system started ``` -3. **Ensure Agent is ready** before app starts (use healthchecks in Docker/K8s) +3. **Ensure Agent is ready** before app starts (use health checks in Docker/Kubernetes) 4. **Check EVP Proxy discovered** in logs: ``` discovered ... evpProxyEndpoint=evp_proxy/v4/ configEndpoint=v0.7/config ``` -5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) +5. **Wait for Remote Configuration sync** (can take 30-60 seconds after publishing flags) 6. **Verify flags are published** in Datadog UI to the correct service and environment ### ClassNotFoundException or NoClassDefFoundError @@ -659,7 +659,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s **Cause**: Application started before Agent was fully initialized **Solutions**: -1. **Add Agent healthcheck** in orchestration (Docker Compose, Kubernetes) +1. **Add Agent health check** in orchestration (Docker Compose, Kubernetes) 2. **Add startup delay** to application 3. **Retry logic**: Implement retry on provider initialization failure 4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support @@ -672,7 +672,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 1. Check Remote Configuration is enabled on both Agent and application 2. Verify Agent can connect to Datadog backend 3. Check application logs for "No configuration changes" or "Configuration received" -4. Ensure flags are published (not just saved as draft) in the Datadog UI +4. Ensure flags are published (not saved as drafts) in the Datadog UI 5. Verify service and environment tags match between app and flag targeting ### Type mismatch errors From ccfb6af80b2d1e8a003789e9d8790c117d2e0e9d Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Mon, 1 Dec 2025 21:11:17 -0700 Subject: [PATCH 09/19] Add Java Feature Flags setup documentation - Add comprehensive Java SDK onboarding guide - Include installation instructions for Maven and Gradle - Document OpenFeature provider initialization and usage - Add code examples for all flag types (boolean, string, int, double, object) - Document error handling and common error codes - Include best practices and troubleshooting guide - Add Java to feature flags setup page navigation - Document integration with Datadog APM and exposure tracking Related: FFE Server SDK code freeze preparation --- content/en/feature_flags/setup/java.md | 560 ++++++++++++++++++ .../feature_flags/feature_flags_setup.html | 7 + 2 files changed, 567 insertions(+) create mode 100644 content/en/feature_flags/setup/java.md diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md new file mode 100644 index 00000000000..05be08e6c85 --- /dev/null +++ b/content/en/feature_flags/setup/java.md @@ -0,0 +1,560 @@ +--- +title: Java Feature Flags +description: Set up Datadog Feature Flags for Java applications. +further_reading: +- link: "/feature_flags/setup/" + tag: "Documentation" + text: "Feature Flags Setup" +- link: "/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/" + tag: "Documentation" + text: "Java APM and Distributed Tracing" +--- + +{{< callout url="http://datadoghq.com/product-preview/feature-flags/" >}} +Feature Flags are in Preview. Complete the form to request access. +{{< /callout >}} + +
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the configuration section for details.
+ +## Overview + +This page describes how to instrument your Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. + +The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. + +## Prerequisites + +- **Java 11 or higher**: The Feature Flags SDK requires Java 11+ +- **Datadog APM Tracer**: Datadog Feature Flags requires `dd-trace-java` version **1.57.0** or later (currently in development) +- **Datadog Agent**: A running Datadog Agent **7.x or later** with Remote Configuration enabled and EVP Proxy support +- **Datadog API Key**: Required for Remote Configuration + +
Note: Feature Flags support is not yet available in the public release of dd-trace-java. You'll need a development build or wait for the official release.
+ +## Installation + +Feature flagging is included in the Datadog Java APM tracer (`dd-trace-java`). No additional dependencies are required beyond the tracer and the OpenFeature SDK. + +{{< tabs >}} +{{% tab "Gradle" %}} +Add the following dependencies to your `build.gradle`: + +{{< code-block lang="groovy" filename="build.gradle" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation 'com.datadoghq:dd-trace-api:X.X.X' + + // OpenFeature SDK for flag evaluation + implementation 'dev.openfeature:sdk:1.18.2' + + // OpenFeature Provider (packaged separately) + implementation 'com.datadoghq:dd-openfeature:X.X.X' +} +{{< /code-block >}} + +Or with Kotlin DSL (`build.gradle.kts`): + +{{< code-block lang="kotlin" filename="build.gradle.kts" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation("com.datadoghq:dd-trace-api:X.X.X") + + // OpenFeature SDK for flag evaluation + implementation("dev.openfeature:sdk:1.18.2") + + // OpenFeature Provider (packaged separately) + implementation("com.datadoghq:dd-openfeature:X.X.X") +} +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Maven" %}} +Add the following dependencies to your `pom.xml`: + +{{< code-block lang="xml" filename="pom.xml" >}} + + + + com.datadoghq + dd-trace-api + X.X.X + + + + + dev.openfeature + sdk + 1.18.2 + + + + + com.datadoghq + dd-openfeature + X.X.X + + +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +## Configuration + +### Agent Configuration + +Configure your Datadog Agent to enable Remote Configuration: + +{{< code-block lang="yaml" filename="datadog.yaml" >}} +# Enable Remote Configuration +remote_configuration: + enabled: true + +# Set your API key +api_key: +{{< /code-block >}} + +### Application Configuration + +Configure your Java application with the required environment variables or system properties: + +{{< tabs >}} +{{% tab "Environment Variables" %}} +{{< code-block lang="bash" >}} +# Required: Enable Remote Configuration in the tracer +export DD_REMOTE_CONFIG_ENABLED=true + +# Required: Enable experimental feature flagging support +export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + +# Required: Your Datadog API key +export DD_API_KEY= + +# Required: Service name +export DD_SERVICE= + +# Required: Environment (e.g., prod, staging, dev) +export DD_ENV= + +# Optional: Version +export DD_VERSION= + +# Start your application with the tracer +java -javaagent:path/to/dd-java-agent.jar -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} + +{{% tab "System Properties" %}} +{{< code-block lang="bash" >}} +java -javaagent:path/to/dd-java-agent.jar \ + -Ddd.remote.config.enabled=true \ + -Ddd.experimental.flagging.provider.enabled=true \ + -Ddd.api.key= \ + -Ddd.service= \ + -Ddd.env= \ + -Ddd.version= \ + -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the Feature Flagging system will not start.
+ +
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+ +## Initialize the OpenFeature Provider + +Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Client; +import datadog.trace.api.openfeature.Provider; + +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; + +// Initialize the Datadog provider +OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + +try { + // Set provider and wait for initial configuration (recommended) + api.setProviderAndWait(new Provider()); +} catch (ProviderNotReadyError e) { + // Handle gracefully - app will use default flag values + System.err.println("Provider not ready: " + e.getMessage()); + // Continue - client will use defaults until configuration arrives +} + +// Get a client for evaluating flags +Client client = api.getClient(); +{{< /code-block >}} + +
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
+ +
Graceful Degradation: If the provider times out, catch ProviderNotReadyError to allow your application to start with default flag values. This prevents application crashes when Remote Config is unavailable.
+ +### Asynchronous Initialization + +For non-blocking initialization, use `setProvider()` and listen for the `PROVIDER_READY` event: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +OpenFeatureAPI api = OpenFeatureAPI.getInstance(); +Client client = api.getClient(); + +// Listen for provider ready event +client.on(ProviderEvent.PROVIDER_READY, (event) -> { + System.out.println("Feature flags ready!"); +}); + +// Set provider asynchronously +api.setProvider(new Provider()); +{{< /code-block >}} + +## Set the Evaluation Context + +The evaluation context defines the subject (user, device, session) for flag evaluation. It determines which flag variations are returned based on targeting rules. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.MutableContext; + +// Create an evaluation context with a targeting key and attributes +EvaluationContext context = new MutableContext("user-123") + .add("email", "user@example.com") + .add("tier", "premium") + .add("country", "US"); + +// Use the context for flag evaluations (see next section) +{{< /code-block >}} + +
Targeting Key: The targetingKey (e.g., "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
+ +## Evaluate Flags + +Evaluate feature flags using the OpenFeature client. All flag types are supported: boolean, string, integer, double, and object. + +### Boolean Flags + +{{< code-block lang="java" >}} +// Simple boolean evaluation +boolean enabled = client.getBooleanValue("new-checkout-flow", false, context); + +if (enabled) { + // New checkout flow +} else { + // Old checkout flow +} + +// Get detailed evaluation result +import dev.openfeature.sdk.FlagEvaluationDetails; + +FlagEvaluationDetails details = + client.getBooleanDetails("new-checkout-flow", false, context); + +System.out.println("Value: " + details.getValue()); +System.out.println("Variant: " + details.getVariant()); +System.out.println("Reason: " + details.getReason()); +{{< /code-block >}} + +### String Flags + +{{< code-block lang="java" >}} +// Evaluate string flags (e.g., UI themes, API endpoints) +String theme = client.getStringValue("ui-theme", "light", context); + +String apiEndpoint = client.getStringValue( + "payment-api-endpoint", + "https://api.example.com/v1", + context +); +{{< /code-block >}} + +### Number Flags + +{{< code-block lang="java" >}} +// Integer flags (e.g., limits, quotas) +int maxRetries = client.getIntegerValue("max-retries", 3, context); + +// Double flags (e.g., thresholds, rates) +double discountRate = client.getDoubleValue("discount-rate", 0.0, context); +{{< /code-block >}} + +### Object Flags + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.Value; + +// Evaluate object/JSON flags for complex configuration +Value config = client.getObjectValue("feature-config", new Value(), context); + +// Access structured data +if (config.isStructure()) { + Value timeout = config.asStructure().getValue("timeout"); + Value endpoint = config.asStructure().getValue("endpoint"); +} +{{< /code-block >}} + +## Error Handling + +The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ErrorCode; + +// Check evaluation details for errors +FlagEvaluationDetails details = + client.getBooleanDetails("my-flag", false, context); + +if (details.getErrorCode() != null) { + switch (details.getErrorCode()) { + case FLAG_NOT_FOUND: + System.err.println("Flag does not exist"); + break; + case PROVIDER_NOT_READY: + System.err.println("Provider not initialized yet"); + break; + case TARGETING_KEY_MISSING: + System.err.println("Evaluation context missing targeting key"); + break; + case TYPE_MISMATCH: + System.err.println("Flag value type doesn't match requested type"); + break; + default: + System.err.println("Evaluation error: " + details.getErrorMessage()); + } +} +{{< /code-block >}} + +### Common Error Codes + +| Error Code | Description | Resolution | +|------------|-------------|------------| +| `PROVIDER_NOT_READY` | Initial configuration not received | Wait for provider initialization or use `setProviderAndWait()` | +| `FLAG_NOT_FOUND` | Flag doesn't exist in configuration | Check flag key or create flag in Datadog UI | +| `TARGETING_KEY_MISSING` | No targeting key in evaluation context | Provide a targeting key when creating context | +| `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | +| `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | + +## Exposure Tracking + +Flag exposures are automatically tracked and sent to Datadog when: +1. A flag is evaluated successfully +2. The flag's allocation has `doLog=true` configured + +Exposures appear in the Datadog UI and can be used for: +- Analyzing feature adoption +- Correlating feature flags with application performance +- Debugging flag behavior + +No additional code is required - exposures are automatically logged by the Datadog tracer integration. + +## Advanced Configuration + +### Custom Initialization Timeout + +Configure how long the provider waits for initial configuration: + +{{< code-block lang="java" >}} +import datadog.trace.api.openfeature.Provider; +import java.util.concurrent.TimeUnit; + +Provider.Options options = new Provider.Options() + .initTimeout(10, TimeUnit.SECONDS); + +api.setProviderAndWait(new Provider(options)); +{{< /code-block >}} + +### Configuration Change Events + +Listen for configuration updates from Remote Config: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { + System.out.println("Flag configuration updated: " + event.getMessage()); + // Optionally re-evaluate flags or trigger cache refresh +}); +{{< /code-block >}} + +### Multiple Clients + +Use named clients to organize flags by domain or team: + +{{< code-block lang="java" >}} +// All clients use the same provider but can have different contexts +Client checkoutClient = api.getClient("checkout"); +Client analyticsClient = api.getClient("analytics"); + +// Each client can have its own evaluation context +EvaluationContext checkoutContext = new MutableContext("session-abc"); +EvaluationContext analyticsContext = new MutableContext("user-123"); + +boolean newCheckout = checkoutClient.getBooleanValue( + "new-checkout-ui", false, checkoutContext +); + +boolean enhancedAnalytics = analyticsClient.getBooleanValue( + "enhanced-analytics", false, analyticsContext +); +{{< /code-block >}} + +## Best Practices + +### 1. Initialize Early +Initialize the OpenFeature provider as early as possible in your application lifecycle (e.g., in `main()` or application startup) to ensure flags are ready before business logic executes. + +### 2. Use Meaningful Default Values +Always provide sensible default values that maintain safe behavior if flag evaluation fails: + +{{< code-block lang="java" >}} +// Good: Safe default that maintains current behavior +boolean useNewAlgorithm = client.getBooleanValue("new-algorithm", false, context); + +// Good: Conservative default for limits +int rateLimit = client.getIntegerValue("rate-limit", 100, context); +{{< /code-block >}} + +### 3. Create Context Once +Create the evaluation context once per request/user/session and reuse it for all flag evaluations: + +{{< code-block lang="java" >}} +// In a web filter or request handler +EvaluationContext userContext = new MutableContext(userId) + .add("email", user.getEmail()) + .add("plan", user.getPlan()); + +// Reuse context for all flags in this request +boolean featureA = client.getBooleanValue("feature-a", false, userContext); +boolean featureB = client.getBooleanValue("feature-b", false, userContext); +{{< /code-block >}} + +### 4. Handle Initialization Failures +Always handle cases where the provider fails to initialize: + +{{< code-block lang="java" >}} +try { + api.setProviderAndWait(new Provider()); +} catch (Exception e) { + // Log error and continue with defaults + logger.error("Failed to initialize feature flags", e); + // Application will use default values for all flags +} +{{< /code-block >}} + +### 5. Use Consistent Targeting Keys +Use consistent, stable identifiers as targeting keys: +- **Good**: User IDs, session IDs, device IDs +- **Avoid**: Timestamps, random values, frequently changing IDs + +### 6. Monitor Flag Evaluation +Use the detailed evaluation results for logging and debugging: + +{{< code-block lang="java" >}} +FlagEvaluationDetails details = + client.getBooleanDetails("critical-feature", false, context); + +logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", + "critical-feature", + details.getValue(), + details.getVariant(), + details.getReason() +); +{{< /code-block >}} + +## Integration with Datadog APM + +Feature flags automatically integrate with Datadog APM: + +- **Trace Correlation**: Flag evaluations are automatically correlated with APM traces +- **Performance Impact**: Track how feature flags affect application performance +- **Error Tracking**: See which flags were active when errors occurred +- **Exposure Analytics**: Analyze feature adoption in the Datadog UI + +No additional configuration is required - this integration is automatic when using the Datadog tracer. + +## Troubleshooting + +### Provider Not Ready + +**Problem**: `PROVIDER_NOT_READY` errors when evaluating flags + +**Common Causes**: +1. **Experimental flag not enabled**: Feature flagging is disabled by default +2. **Agent not ready**: Application started before Agent was fully initialized +3. **No flags configured**: No flags published to your service/environment combination +4. **Agent Remote Config disabled**: Agent not configured for Remote Configuration + +**Solutions**: +1. **Enable experimental feature**: + ```bash + export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + ``` +2. **Verify Feature Flagging system started** in application logs: + ``` + [dd.trace] Feature Flagging system starting + [dd.trace] Feature Flagging system started + ``` +3. **Ensure Agent is ready** before app starts (use healthchecks in Docker/K8s) +4. **Check EVP Proxy discovered** in logs: + ``` + discovered ... evpProxyEndpoint=evp_proxy/v4/ configEndpoint=v0.7/config + ``` +5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) +6. **Verify flags are published** in Datadog UI to the correct service and environment + +### Feature Flagging System Not Starting + +**Problem**: No "Feature Flagging system starting" messages in logs + +**Cause**: Experimental flag not enabled in tracer + +**Solution**: +Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` + +### EVP Proxy Not Available Error + +**Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" + +**Cause**: Application started before Agent was fully initialized + +**Solutions**: +1. **Add Agent healthcheck** in orchestration (Docker Compose, Kubernetes) +2. **Add startup delay** to application +3. **Retry logic**: Implement retry on provider initialization failure +4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support + +### Flags Not Updating + +**Problem**: Flag configuration changes aren't reflected in the application + +**Solutions**: +1. Check Remote Configuration is enabled on both Agent and application +2. Verify Agent can connect to Datadog backend +3. Check application logs for "No configuration changes" or "Configuration received" +4. Ensure flags are published (not just saved as draft) in the Datadog UI +5. Verify service and environment tags match between app and flag targeting + +### Type Mismatch Errors + +**Problem**: `TYPE_MISMATCH` errors when evaluating flags + +**Solutions**: +1. Verify the flag type in Datadog UI matches the evaluation method +2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` +3. Check flag configuration for correct value types + +### No Exposures in Datadog + +**Problem**: Flag evaluations aren't appearing in Datadog UI + +**Solutions**: +1. Verify the flag's allocation has `doLog=true` configured +2. Check Datadog Agent is receiving exposure events +3. Verify `DD_API_KEY` is correct +4. Check Agent logs for exposure upload errors + +## Further Reading + +{{< partial name="whats-next/whats-next.html" >}} diff --git a/layouts/partials/feature_flags/feature_flags_setup.html b/layouts/partials/feature_flags/feature_flags_setup.html index c4f9b4cf6de..91879f94dda 100644 --- a/layouts/partials/feature_flags/feature_flags_setup.html +++ b/layouts/partials/feature_flags/feature_flags_setup.html @@ -30,6 +30,13 @@ + From 98611d23ac79f7bfd73dcc19d002a474f70f7fdb Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 11:20:42 -0700 Subject: [PATCH 10/19] fix(docs): Add missing bootstrap JAR and improve Java Feature Flags documentation Critical fixes: - Add missing dd-java-agent-feature-flagging-bootstrap dependency to all examples - Add Building from Source section with detailed instructions - Explain bootstrap module purpose (classloader communication) - Add troubleshooting for ClassNotFoundException Improvements to match Java APM documentation patterns: - Add application server configuration tabs (Spring Boot, Tomcat, JBoss, Jetty, Docker) - Update all code examples to use SLF4J logger instead of System.out/err - Add Compatibility requirements section - Add Getting started section - Update initialization example to show complete application structure Code quality: - Use logger.info/warn/error throughout with parameterized messages - Show proper exception handling with logger - Match actual implementation in ffe-dogfooding repo - Include named client usage ("my-app") The documentation now accurately reflects the required dependencies, follows established Datadog docs patterns, and provides complete setup instructions for the development build. --- content/en/feature_flags/setup/java.md | 237 +++++++++++++++++++++---- 1 file changed, 203 insertions(+), 34 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 05be08e6c85..3f5a7da0d5e 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -22,18 +22,25 @@ This page describes how to instrument your Java application with the Datadog Fea The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. -## Prerequisites +## Compatibility requirements -- **Java 11 or higher**: The Feature Flags SDK requires Java 11+ -- **Datadog APM Tracer**: Datadog Feature Flags requires `dd-trace-java` version **1.57.0** or later (currently in development) -- **Datadog Agent**: A running Datadog Agent **7.x or later** with Remote Configuration enabled and EVP Proxy support +The Datadog Feature Flags SDK for Java requires: +- **Java 11 or higher** +- **Datadog Java APM Tracer**: Version **1.57.0** or later +- **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled - **Datadog API Key**: Required for Remote Configuration -
Note: Feature Flags support is not yet available in the public release of dd-trace-java. You'll need a development build or wait for the official release.
+For a full list of Datadog's Java version and framework support, read [Compatibility Requirements](/tracing/trace_collection/compatibility/java/). + +## Getting started + +Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). + +
Development Build Required: Feature Flags support is currently available in development builds of dd-trace-java version 1.57.0. It will be included in an upcoming public release. See the Building from Source section for instructions.
## Installation -Feature flagging is included in the Datadog Java APM tracer (`dd-trace-java`). No additional dependencies are required beyond the tracer and the OpenFeature SDK. +Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. {{< tabs >}} {{% tab "Gradle" %}} @@ -47,8 +54,11 @@ dependencies { // OpenFeature SDK for flag evaluation implementation 'dev.openfeature:sdk:1.18.2' - // OpenFeature Provider (packaged separately) + // Datadog OpenFeature Provider implementation 'com.datadoghq:dd-openfeature:X.X.X' + + // Datadog Feature Flagging Bootstrap (required) + implementation 'com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X' } {{< /code-block >}} @@ -62,8 +72,11 @@ dependencies { // OpenFeature SDK for flag evaluation implementation("dev.openfeature:sdk:1.18.2") - // OpenFeature Provider (packaged separately) + // Datadog OpenFeature Provider implementation("com.datadoghq:dd-openfeature:X.X.X") + + // Datadog Feature Flagging Bootstrap (required) + implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") } {{< /code-block >}} {{% /tab %}} @@ -87,17 +100,71 @@ Add the following dependencies to your `pom.xml`: 1.18.2 - + com.datadoghq dd-openfeature X.X.X + + + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + {{< /code-block >}} {{% /tab %}} {{< /tabs >}} +### Building from Source + +
Temporary Requirement: Until the Feature Flags artifacts are published to Maven Central, you'll need to build them locally from the dd-trace-java repository.
+ +1. Clone the `dd-trace-java` repository: + +{{< code-block lang="bash" >}} +git clone https://github.com/DataDog/dd-trace-java.git +cd dd-trace-java +{{< /code-block >}} + +2. Build the Feature Flagging modules: + +{{< code-block lang="bash" >}} +./gradlew :products:feature-flagging:api:jar :products:feature-flagging:bootstrap:jar +{{< /code-block >}} + +3. The built JARs will be located at: + - `products/feature-flagging/api/build/libs/dd-openfeature-X.X.X-SNAPSHOT.jar` + - `products/feature-flagging/bootstrap/build/libs/bootstrap-X.X.X-SNAPSHOT.jar` + +4. Use these JARs with system scope in your `pom.xml` or copy them to your project's `libs/` directory: + +{{< code-block lang="xml" filename="pom.xml" >}} + + + + com.datadoghq + dd-openfeature + 1.57.0-SNAPSHOT + system + ${project.basedir}/libs/dd-openfeature-1.57.0-SNAPSHOT.jar + + + + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + 1.57.0-SNAPSHOT + system + ${project.basedir}/libs/bootstrap-1.57.0-SNAPSHOT.jar + + +{{< /code-block >}} + +
Note: The bootstrap JAR contains shared interfaces between the tracer and OpenFeature provider. It enables communication across Java classloader boundaries. Both JARs are required for feature flags to work.
+ ## Configuration ### Agent Configuration @@ -161,6 +228,72 @@ java -javaagent:path/to/dd-java-agent.jar \
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+### Add the Java Tracer to the JVM + +Use the documentation for your application server to figure out the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: + +{{< tabs >}} +{{% tab "Spring Boot" %}} + +If your app is called `my_app.jar`, create a `my_app.conf`, containing: + +```text +JAVA_OPTS=-javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true +``` + +For more information, see the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-script-customization-when-it-runs). + +{{% /tab %}} +{{% tab "Tomcat" %}} + +Open your Tomcat startup script file, for example `setenv.sh`, and add: + +```text +CATALINA_OPTS="$CATALINA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "JBoss" %}} + +Add the following line to the end of `standalone.conf`: + +```text +JAVA_OPTS="$JAVA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "Jetty" %}} + +Add the following to your `start.ini`: + +```text +--exec +-javaagent:/path/to/dd-java-agent.jar +-Ddd.remote.config.enabled=true +-Ddd.experimental.flagging.provider.enabled=true +``` + +{{% /tab %}} +{{% tab "Docker" %}} + +Update your Dockerfile to download and use the tracer: + +```dockerfile +FROM openjdk:17-jre + +# Download the Datadog Java tracer +ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar + +# Copy your application +COPY myapp.jar . + +# Run with feature flags enabled +CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", "-Ddd.experimental.flagging.provider.enabled=true", "-jar", "myapp.jar"] +``` + +{{% /tab %}} +{{< /tabs >}} + ## Initialize the OpenFeature Provider Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. @@ -169,23 +302,37 @@ Initialize the Datadog OpenFeature provider in your application startup code. Th import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Client; import datadog.trace.api.openfeature.Provider; - import dev.openfeature.sdk.exceptions.ProviderNotReadyError; - -// Initialize the Datadog provider -OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - -try { - // Set provider and wait for initial configuration (recommended) - api.setProviderAndWait(new Provider()); -} catch (ProviderNotReadyError e) { - // Handle gracefully - app will use default flag values - System.err.println("Provider not ready: " + e.getMessage()); - // Continue - client will use defaults until configuration arrives +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + private static final Logger logger = LoggerFactory.getLogger(App.class); + private static Client client; + + public static void main(String[] args) throws Exception { + // Initialize the Datadog provider + logger.info("Initializing Datadog OpenFeature Provider..."); + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + + try { + // Set provider and wait for initial configuration (recommended) + api.setProviderAndWait(new Provider()); + client = api.getClient("my-app"); + logger.info("OpenFeature provider initialized successfully"); + } catch (ProviderNotReadyError e) { + // Handle gracefully - app will use default flag values + logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); + client = api.getClient("my-app"); + logger.info("App will use default flag values until provider is ready"); + } catch (Exception e) { + logger.error("Failed to initialize OpenFeature provider", e); + throw e; + } + + // Your application code here + } } - -// Get a client for evaluating flags -Client client = api.getClient(); {{< /code-block >}}
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
@@ -204,7 +351,7 @@ Client client = api.getClient(); // Listen for provider ready event client.on(ProviderEvent.PROVIDER_READY, (event) -> { - System.out.println("Feature flags ready!"); + logger.info("Feature flags ready!"); }); // Set provider asynchronously @@ -252,9 +399,9 @@ import dev.openfeature.sdk.FlagEvaluationDetails; FlagEvaluationDetails details = client.getBooleanDetails("new-checkout-flow", false, context); -System.out.println("Value: " + details.getValue()); -System.out.println("Variant: " + details.getVariant()); -System.out.println("Reason: " + details.getReason()); +logger.info("Value: {}", details.getValue()); +logger.info("Variant: {}", details.getVariant()); +logger.info("Reason: {}", details.getReason()); {{< /code-block >}} ### String Flags @@ -309,19 +456,19 @@ FlagEvaluationDetails details = if (details.getErrorCode() != null) { switch (details.getErrorCode()) { case FLAG_NOT_FOUND: - System.err.println("Flag does not exist"); + logger.warn("Flag does not exist: {}", "my-flag"); break; case PROVIDER_NOT_READY: - System.err.println("Provider not initialized yet"); + logger.warn("Provider not initialized yet"); break; case TARGETING_KEY_MISSING: - System.err.println("Evaluation context missing targeting key"); + logger.warn("Evaluation context missing targeting key"); break; case TYPE_MISMATCH: - System.err.println("Flag value type doesn't match requested type"); + logger.error("Flag value type doesn't match requested type"); break; default: - System.err.println("Evaluation error: " + details.getErrorMessage()); + logger.error("Evaluation error for flag: {}", "my-flag", details.getErrorCode()); } } {{< /code-block >}} @@ -373,7 +520,7 @@ Listen for configuration updates from Remote Config: import dev.openfeature.sdk.ProviderEvent; client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { - System.out.println("Flag configuration updated: " + event.getMessage()); + logger.info("Flag configuration updated: {}", event.getMessage()); // Optionally re-evaluate flags or trigger cache refresh }); {{< /code-block >}} @@ -504,6 +651,28 @@ No additional configuration is required - this integration is automatic when usi 5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) 6. **Verify flags are published** in Datadog UI to the correct service and environment +### ClassNotFoundException or NoClassDefFoundError + +**Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` + +**Cause**: Missing the bootstrap JAR dependency + +**Solutions**: +1. **Add the bootstrap JAR** to your dependencies: + ```xml + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + + ``` +2. **Verify both JARs are present** if building locally: + - `dd-openfeature-X.X.X.jar` (the provider) + - `bootstrap-X.X.X.jar` (the bootstrap module) +3. **Check the classpath** includes both JARs in your runtime configuration + +
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
+ ### Feature Flagging System Not Starting **Problem**: No "Feature Flagging system starting" messages in logs From 5b753b8f4bceaad5867b7aacb3a6878fe6dec5da Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:08:20 -0700 Subject: [PATCH 11/19] docs(java): Improve documentation based on review feedback Changes: - Add cross-linking to Configuration section from early warning - Add OpenFeature SDK to compatibility requirements (it is required) - Remove all local build instructions (only reference published X.X.X versions) - Add skip guidance for users with existing agent/remote-config setup - Clarify ProviderNotReadyError is OpenFeature exception (optional handling) - Update exception handling to be optional based on availability requirements - Note that not catching exception may prevent application startup Improvements: - Users with existing APM can skip to provider initialization - Users with existing remote-config can skip agent configuration - Clear guidance on when exception handling is optional vs required - Removed all references to building from source --- content/en/feature_flags/setup/java.md | 81 +++++++------------------- 1 file changed, 20 insertions(+), 61 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 3f5a7da0d5e..60bdb8eee5a 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -14,7 +14,7 @@ further_reading: Feature Flags are in Preview. Complete the form to request access. {{< /callout >}} -
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the configuration section for details.
+
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
## Overview @@ -22,11 +22,14 @@ This page describes how to instrument your Java application with the Datadog Fea The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. +
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
+ ## Compatibility requirements The Datadog Feature Flags SDK for Java requires: - **Java 11 or higher** - **Datadog Java APM Tracer**: Version **1.57.0** or later +- **OpenFeature SDK**: Version **1.18.2** or later - **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled - **Datadog API Key**: Required for Remote Configuration @@ -36,8 +39,6 @@ For a full list of Datadog's Java version and framework support, read [Compatibi Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). -
Development Build Required: Feature Flags support is currently available in development builds of dd-trace-java version 1.57.0. It will be included in an upcoming public release. See the Building from Source section for instructions.
- ## Installation Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. @@ -118,55 +119,12 @@ Add the following dependencies to your `pom.xml`: {{% /tab %}} {{< /tabs >}} -### Building from Source - -
Temporary Requirement: Until the Feature Flags artifacts are published to Maven Central, you'll need to build them locally from the dd-trace-java repository.
- -1. Clone the `dd-trace-java` repository: - -{{< code-block lang="bash" >}} -git clone https://github.com/DataDog/dd-trace-java.git -cd dd-trace-java -{{< /code-block >}} - -2. Build the Feature Flagging modules: - -{{< code-block lang="bash" >}} -./gradlew :products:feature-flagging:api:jar :products:feature-flagging:bootstrap:jar -{{< /code-block >}} - -3. The built JARs will be located at: - - `products/feature-flagging/api/build/libs/dd-openfeature-X.X.X-SNAPSHOT.jar` - - `products/feature-flagging/bootstrap/build/libs/bootstrap-X.X.X-SNAPSHOT.jar` - -4. Use these JARs with system scope in your `pom.xml` or copy them to your project's `libs/` directory: - -{{< code-block lang="xml" filename="pom.xml" >}} - - - - com.datadoghq - dd-openfeature - 1.57.0-SNAPSHOT - system - ${project.basedir}/libs/dd-openfeature-1.57.0-SNAPSHOT.jar - - - - - com.datadoghq - dd-java-agent-feature-flagging-bootstrap - 1.57.0-SNAPSHOT - system - ${project.basedir}/libs/bootstrap-1.57.0-SNAPSHOT.jar - - -{{< /code-block >}} - -
Note: The bootstrap JAR contains shared interfaces between the tracer and OpenFeature provider. It enables communication across Java classloader boundaries. Both JARs are required for feature flags to work.
+
What is the bootstrap JAR? The dd-java-agent-feature-flagging-bootstrap JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work.
## Configuration +
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
+ ### Agent Configuration Configure your Datadog Agent to enable Remote Configuration: @@ -182,6 +140,8 @@ api_key: ### Application Configuration +
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
+ Configure your Java application with the required environment variables or system properties: {{< tabs >}} @@ -321,13 +281,10 @@ public class App { client = api.getClient("my-app"); logger.info("OpenFeature provider initialized successfully"); } catch (ProviderNotReadyError e) { - // Handle gracefully - app will use default flag values + // Optional: Handle gracefully - app will use default flag values logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); client = api.getClient("my-app"); logger.info("App will use default flag values until provider is ready"); - } catch (Exception e) { - logger.error("Failed to initialize OpenFeature provider", e); - throw e; } // Your application code here @@ -337,7 +294,7 @@ public class App {
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
-
Graceful Degradation: If the provider times out, catch ProviderNotReadyError to allow your application to start with default flag values. This prevents application crashes when Remote Config is unavailable.
+
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows your application to start with default flag values if Remote Config is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
### Asynchronous Initialization @@ -577,19 +534,21 @@ boolean featureA = client.getBooleanValue("feature-a", false, userContext); boolean featureB = client.getBooleanValue("feature-b", false, userContext); {{< /code-block >}} -### 4. Handle Initialization Failures -Always handle cases where the provider fails to initialize: +### 4. Handle Initialization Failures (Optional) +Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} try { api.setProviderAndWait(new Provider()); -} catch (Exception e) { +} catch (ProviderNotReadyError e) { // Log error and continue with defaults - logger.error("Failed to initialize feature flags", e); + logger.warn("Feature flags not ready, using defaults", e); // Application will use default values for all flags } {{< /code-block >}} +If feature flags are critical for your application to function, let the exception propagate to prevent startup. + ### 5. Use Consistent Targeting Keys Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs @@ -666,9 +625,9 @@ No additional configuration is required - this integration is automatic when usi X.X.X ``` -2. **Verify both JARs are present** if building locally: - - `dd-openfeature-X.X.X.jar` (the provider) - - `bootstrap-X.X.X.jar` (the bootstrap module) +2. **Verify both dependencies are included** in your build: + - `dd-openfeature` (the OpenFeature provider) + - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) 3. **Check the classpath** includes both JARs in your runtime configuration
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
From 81656dc0bcc8e65bd7257f7d8e1c80070aa2e5d1 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:11:21 -0700 Subject: [PATCH 12/19] docs(java): Add event state watching and clarify provider instance sharing Changes: - Add PROVIDER_ERROR and PROVIDER_STALE event listeners to async init example - Note that PROVIDER_CONFIGURATION_CHANGED is optional (depends on provider support) - Update multiple clients section: "organize context and flags" (not just flags) - Clarify that Provider instance is shared globally (client names are organizational only) The Provider constructor doesn't take a name parameter - it's a single shared instance. Named clients are just for organizing your application code, not separate providers. --- content/en/feature_flags/setup/java.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 60bdb8eee5a..0da13a917aa 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -298,7 +298,7 @@ public class App { ### Asynchronous Initialization -For non-blocking initialization, use `setProvider()` and listen for the `PROVIDER_READY` event: +For non-blocking initialization, use `setProvider()` and listen for provider events: {{< code-block lang="java" >}} import dev.openfeature.sdk.ProviderEvent; @@ -306,11 +306,19 @@ import dev.openfeature.sdk.ProviderEvent; OpenFeatureAPI api = OpenFeatureAPI.getInstance(); Client client = api.getClient(); -// Listen for provider ready event +// Listen for provider state changes client.on(ProviderEvent.PROVIDER_READY, (event) -> { logger.info("Feature flags ready!"); }); +client.on(ProviderEvent.PROVIDER_ERROR, (event) -> { + logger.error("Provider error: {}", event.getMessage()); +}); + +client.on(ProviderEvent.PROVIDER_STALE, (event) -> { + logger.warn("Provider configuration is stale"); +}); + // Set provider asynchronously api.setProvider(new Provider()); {{< /code-block >}} @@ -482,12 +490,14 @@ client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { }); {{< /code-block >}} +
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
+ ### Multiple Clients -Use named clients to organize flags by domain or team: +Use named clients to organize context and flags by domain or team: {{< code-block lang="java" >}} -// All clients use the same provider but can have different contexts +// Named clients share the same provider instance but can have different contexts Client checkoutClient = api.getClient("checkout"); Client analyticsClient = api.getClient("analytics"); @@ -504,6 +514,8 @@ boolean enhancedAnalytics = analyticsClient.getBooleanValue( ); {{< /code-block >}} +
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
+ ## Best Practices ### 1. Initialize Early From e4fdaffb27b10ac472ca1c26c7540ddf5dff7d85 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Fri, 5 Dec 2025 12:25:14 -0700 Subject: [PATCH 13/19] docs(java): Align flag keys and attributes with mobile SDK conventions Changes: - Update all flag keys to use dot notation (matching Android/iOS): * new-checkout-flow -> checkout.new * ui-theme -> ui.theme * payment-api-endpoint -> payment.api.endpoint * max-retries -> retries.max * discount-rate -> pricing.discount.rate * feature-config -> ui.config * All other flags updated to dot notation - Standardize attribute to use "tier" consistently (not "plan") - Remove "country" attribute from basic example to match mobile simplicity This creates consistency across all SDK documentation and makes it easier for users to migrate between platforms or reference examples. --- content/en/feature_flags/setup/java.md | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md index 0da13a917aa..13f89596ad3 100644 --- a/content/en/feature_flags/setup/java.md +++ b/content/en/feature_flags/setup/java.md @@ -334,8 +334,7 @@ import dev.openfeature.sdk.MutableContext; // Create an evaluation context with a targeting key and attributes EvaluationContext context = new MutableContext("user-123") .add("email", "user@example.com") - .add("tier", "premium") - .add("country", "US"); + .add("tier", "premium"); // Use the context for flag evaluations (see next section) {{< /code-block >}} @@ -350,7 +349,7 @@ Evaluate feature flags using the OpenFeature client. All flag types are supporte {{< code-block lang="java" >}} // Simple boolean evaluation -boolean enabled = client.getBooleanValue("new-checkout-flow", false, context); +boolean enabled = client.getBooleanValue("checkout.new", false, context); if (enabled) { // New checkout flow @@ -362,7 +361,7 @@ if (enabled) { import dev.openfeature.sdk.FlagEvaluationDetails; FlagEvaluationDetails details = - client.getBooleanDetails("new-checkout-flow", false, context); + client.getBooleanDetails("checkout.new", false, context); logger.info("Value: {}", details.getValue()); logger.info("Variant: {}", details.getVariant()); @@ -373,10 +372,10 @@ logger.info("Reason: {}", details.getReason()); {{< code-block lang="java" >}} // Evaluate string flags (e.g., UI themes, API endpoints) -String theme = client.getStringValue("ui-theme", "light", context); +String theme = client.getStringValue("ui.theme", "light", context); String apiEndpoint = client.getStringValue( - "payment-api-endpoint", + "payment.api.endpoint", "https://api.example.com/v1", context ); @@ -386,10 +385,10 @@ String apiEndpoint = client.getStringValue( {{< code-block lang="java" >}} // Integer flags (e.g., limits, quotas) -int maxRetries = client.getIntegerValue("max-retries", 3, context); +int maxRetries = client.getIntegerValue("retries.max", 3, context); // Double flags (e.g., thresholds, rates) -double discountRate = client.getDoubleValue("discount-rate", 0.0, context); +double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); {{< /code-block >}} ### Object Flags @@ -398,7 +397,7 @@ double discountRate = client.getDoubleValue("discount-rate", 0.0, context); import dev.openfeature.sdk.Value; // Evaluate object/JSON flags for complex configuration -Value config = client.getObjectValue("feature-config", new Value(), context); +Value config = client.getObjectValue("ui.config", new Value(), context); // Access structured data if (config.isStructure()) { @@ -416,12 +415,12 @@ import dev.openfeature.sdk.ErrorCode; // Check evaluation details for errors FlagEvaluationDetails details = - client.getBooleanDetails("my-flag", false, context); + client.getBooleanDetails("checkout.new", false, context); if (details.getErrorCode() != null) { switch (details.getErrorCode()) { case FLAG_NOT_FOUND: - logger.warn("Flag does not exist: {}", "my-flag"); + logger.warn("Flag does not exist: {}", "checkout.new"); break; case PROVIDER_NOT_READY: logger.warn("Provider not initialized yet"); @@ -433,7 +432,7 @@ if (details.getErrorCode() != null) { logger.error("Flag value type doesn't match requested type"); break; default: - logger.error("Evaluation error for flag: {}", "my-flag", details.getErrorCode()); + logger.error("Evaluation error for flag: {}", "checkout.new", details.getErrorCode()); } } {{< /code-block >}} @@ -506,11 +505,11 @@ EvaluationContext checkoutContext = new MutableContext("session-abc"); EvaluationContext analyticsContext = new MutableContext("user-123"); boolean newCheckout = checkoutClient.getBooleanValue( - "new-checkout-ui", false, checkoutContext + "checkout.ui.new", false, checkoutContext ); boolean enhancedAnalytics = analyticsClient.getBooleanValue( - "enhanced-analytics", false, analyticsContext + "analytics.enhanced", false, analyticsContext ); {{< /code-block >}} @@ -526,10 +525,10 @@ Always provide sensible default values that maintain safe behavior if flag evalu {{< code-block lang="java" >}} // Good: Safe default that maintains current behavior -boolean useNewAlgorithm = client.getBooleanValue("new-algorithm", false, context); +boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context); // Good: Conservative default for limits -int rateLimit = client.getIntegerValue("rate-limit", 100, context); +int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} ### 3. Create Context Once @@ -539,11 +538,11 @@ Create the evaluation context once per request/user/session and reuse it for all // In a web filter or request handler EvaluationContext userContext = new MutableContext(userId) .add("email", user.getEmail()) - .add("plan", user.getPlan()); + .add("tier", user.getTier()); // Reuse context for all flags in this request -boolean featureA = client.getBooleanValue("feature-a", false, userContext); -boolean featureB = client.getBooleanValue("feature-b", false, userContext); +boolean featureA = client.getBooleanValue("feature.a", false, userContext); +boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} ### 4. Handle Initialization Failures (Optional) @@ -571,10 +570,10 @@ Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} FlagEvaluationDetails details = - client.getBooleanDetails("critical-feature", false, context); + client.getBooleanDetails("feature.critical", false, context); logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - "critical-feature", + "feature.critical", details.getValue(), details.getVariant(), details.getReason() From 0b60ba0af915a7482eeb27ae174ec1d9d1e298fa Mon Sep 17 00:00:00 2001 From: Joe Peeples Date: Mon, 8 Dec 2025 16:10:11 -0700 Subject: [PATCH 14/19] docs(java): Move Java Feature Flags docs to server/ directory - Move java.md from feature_flags/setup/ to feature_flags/setup/server/ - Update frontmatter to reference server-side landing page - Apply style edits from linter (sentence-case headings, consistent capitalization) --- content/en/feature_flags/setup/java.md | 699 ------------------ content/en/feature_flags/setup/server/java.md | 682 ++++++++++++++++- 2 files changed, 678 insertions(+), 703 deletions(-) delete mode 100644 content/en/feature_flags/setup/java.md diff --git a/content/en/feature_flags/setup/java.md b/content/en/feature_flags/setup/java.md deleted file mode 100644 index 13f89596ad3..00000000000 --- a/content/en/feature_flags/setup/java.md +++ /dev/null @@ -1,699 +0,0 @@ ---- -title: Java Feature Flags -description: Set up Datadog Feature Flags for Java applications. -further_reading: -- link: "/feature_flags/setup/" - tag: "Documentation" - text: "Feature Flags Setup" -- link: "/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/" - tag: "Documentation" - text: "Java APM and Distributed Tracing" ---- - -{{< callout url="http://datadoghq.com/product-preview/feature-flags/" >}} -Feature Flags are in Preview. Complete the form to request access. -{{< /callout >}} - -
Experimental Feature: Java Feature Flags support is currently experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
- -## Overview - -This page describes how to instrument your Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. - -The Java SDK integrates feature flags directly into your Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. - -
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
- -## Compatibility requirements - -The Datadog Feature Flags SDK for Java requires: -- **Java 11 or higher** -- **Datadog Java APM Tracer**: Version **1.57.0** or later -- **OpenFeature SDK**: Version **1.18.2** or later -- **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled -- **Datadog API Key**: Required for Remote Configuration - -For a full list of Datadog's Java version and framework support, read [Compatibility Requirements](/tracing/trace_collection/compatibility/java/). - -## Getting started - -Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). - -## Installation - -Feature flagging is integrated into the Datadog Java APM tracer. You'll need the tracer JAR and the OpenFeature SDK dependencies. - -{{< tabs >}} -{{% tab "Gradle" %}} -Add the following dependencies to your `build.gradle`: - -{{< code-block lang="groovy" filename="build.gradle" >}} -dependencies { - // Datadog Java tracer (includes feature flagging) - implementation 'com.datadoghq:dd-trace-api:X.X.X' - - // OpenFeature SDK for flag evaluation - implementation 'dev.openfeature:sdk:1.18.2' - - // Datadog OpenFeature Provider - implementation 'com.datadoghq:dd-openfeature:X.X.X' - - // Datadog Feature Flagging Bootstrap (required) - implementation 'com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X' -} -{{< /code-block >}} - -Or with Kotlin DSL (`build.gradle.kts`): - -{{< code-block lang="kotlin" filename="build.gradle.kts" >}} -dependencies { - // Datadog Java tracer (includes feature flagging) - implementation("com.datadoghq:dd-trace-api:X.X.X") - - // OpenFeature SDK for flag evaluation - implementation("dev.openfeature:sdk:1.18.2") - - // Datadog OpenFeature Provider - implementation("com.datadoghq:dd-openfeature:X.X.X") - - // Datadog Feature Flagging Bootstrap (required) - implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") -} -{{< /code-block >}} -{{% /tab %}} - -{{% tab "Maven" %}} -Add the following dependencies to your `pom.xml`: - -{{< code-block lang="xml" filename="pom.xml" >}} - - - - com.datadoghq - dd-trace-api - X.X.X - - - - - dev.openfeature - sdk - 1.18.2 - - - - - com.datadoghq - dd-openfeature - X.X.X - - - - - com.datadoghq - dd-java-agent-feature-flagging-bootstrap - X.X.X - - -{{< /code-block >}} -{{% /tab %}} -{{< /tabs >}} - -
What is the bootstrap JAR? The dd-java-agent-feature-flagging-bootstrap JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work.
- -## Configuration - -
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
- -### Agent Configuration - -Configure your Datadog Agent to enable Remote Configuration: - -{{< code-block lang="yaml" filename="datadog.yaml" >}} -# Enable Remote Configuration -remote_configuration: - enabled: true - -# Set your API key -api_key: -{{< /code-block >}} - -### Application Configuration - -
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
- -Configure your Java application with the required environment variables or system properties: - -{{< tabs >}} -{{% tab "Environment Variables" %}} -{{< code-block lang="bash" >}} -# Required: Enable Remote Configuration in the tracer -export DD_REMOTE_CONFIG_ENABLED=true - -# Required: Enable experimental feature flagging support -export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true - -# Required: Your Datadog API key -export DD_API_KEY= - -# Required: Service name -export DD_SERVICE= - -# Required: Environment (e.g., prod, staging, dev) -export DD_ENV= - -# Optional: Version -export DD_VERSION= - -# Start your application with the tracer -java -javaagent:path/to/dd-java-agent.jar -jar your-application.jar -{{< /code-block >}} -{{% /tab %}} - -{{% tab "System Properties" %}} -{{< code-block lang="bash" >}} -java -javaagent:path/to/dd-java-agent.jar \ - -Ddd.remote.config.enabled=true \ - -Ddd.experimental.flagging.provider.enabled=true \ - -Ddd.api.key= \ - -Ddd.service= \ - -Ddd.env= \ - -Ddd.version= \ - -jar your-application.jar -{{< /code-block >}} -{{% /tab %}} -{{< /tabs >}} - -
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the Feature Flagging system will not start.
- -
Note: The Datadog Feature Flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
- -### Add the Java Tracer to the JVM - -Use the documentation for your application server to figure out the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: - -{{< tabs >}} -{{% tab "Spring Boot" %}} - -If your app is called `my_app.jar`, create a `my_app.conf`, containing: - -```text -JAVA_OPTS=-javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true -``` - -For more information, see the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-script-customization-when-it-runs). - -{{% /tab %}} -{{% tab "Tomcat" %}} - -Open your Tomcat startup script file, for example `setenv.sh`, and add: - -```text -CATALINA_OPTS="$CATALINA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" -``` - -{{% /tab %}} -{{% tab "JBoss" %}} - -Add the following line to the end of `standalone.conf`: - -```text -JAVA_OPTS="$JAVA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" -``` - -{{% /tab %}} -{{% tab "Jetty" %}} - -Add the following to your `start.ini`: - -```text ---exec --javaagent:/path/to/dd-java-agent.jar --Ddd.remote.config.enabled=true --Ddd.experimental.flagging.provider.enabled=true -``` - -{{% /tab %}} -{{% tab "Docker" %}} - -Update your Dockerfile to download and use the tracer: - -```dockerfile -FROM openjdk:17-jre - -# Download the Datadog Java tracer -ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar - -# Copy your application -COPY myapp.jar . - -# Run with feature flags enabled -CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", "-Ddd.experimental.flagging.provider.enabled=true", "-jar", "myapp.jar"] -``` - -{{% /tab %}} -{{< /tabs >}} - -## Initialize the OpenFeature Provider - -Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.OpenFeatureAPI; -import dev.openfeature.sdk.Client; -import datadog.trace.api.openfeature.Provider; -import dev.openfeature.sdk.exceptions.ProviderNotReadyError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class App { - private static final Logger logger = LoggerFactory.getLogger(App.class); - private static Client client; - - public static void main(String[] args) throws Exception { - // Initialize the Datadog provider - logger.info("Initializing Datadog OpenFeature Provider..."); - OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - - try { - // Set provider and wait for initial configuration (recommended) - api.setProviderAndWait(new Provider()); - client = api.getClient("my-app"); - logger.info("OpenFeature provider initialized successfully"); - } catch (ProviderNotReadyError e) { - // Optional: Handle gracefully - app will use default flag values - logger.warn("Provider not ready (no tracer/config available), continuing with defaults", e); - client = api.getClient("my-app"); - logger.info("App will use default flag values until provider is ready"); - } - - // Your application code here - } -} -{{< /code-block >}} - -
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Config. This ensures flags are ready before your application starts serving traffic. The default timeout is 30 seconds.
- -
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows your application to start with default flag values if Remote Config is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
- -### Asynchronous Initialization - -For non-blocking initialization, use `setProvider()` and listen for provider events: - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.ProviderEvent; - -OpenFeatureAPI api = OpenFeatureAPI.getInstance(); -Client client = api.getClient(); - -// Listen for provider state changes -client.on(ProviderEvent.PROVIDER_READY, (event) -> { - logger.info("Feature flags ready!"); -}); - -client.on(ProviderEvent.PROVIDER_ERROR, (event) -> { - logger.error("Provider error: {}", event.getMessage()); -}); - -client.on(ProviderEvent.PROVIDER_STALE, (event) -> { - logger.warn("Provider configuration is stale"); -}); - -// Set provider asynchronously -api.setProvider(new Provider()); -{{< /code-block >}} - -## Set the Evaluation Context - -The evaluation context defines the subject (user, device, session) for flag evaluation. It determines which flag variations are returned based on targeting rules. - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.EvaluationContext; -import dev.openfeature.sdk.MutableContext; - -// Create an evaluation context with a targeting key and attributes -EvaluationContext context = new MutableContext("user-123") - .add("email", "user@example.com") - .add("tier", "premium"); - -// Use the context for flag evaluations (see next section) -{{< /code-block >}} - -
Targeting Key: The targetingKey (e.g., "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
- -## Evaluate Flags - -Evaluate feature flags using the OpenFeature client. All flag types are supported: boolean, string, integer, double, and object. - -### Boolean Flags - -{{< code-block lang="java" >}} -// Simple boolean evaluation -boolean enabled = client.getBooleanValue("checkout.new", false, context); - -if (enabled) { - // New checkout flow -} else { - // Old checkout flow -} - -// Get detailed evaluation result -import dev.openfeature.sdk.FlagEvaluationDetails; - -FlagEvaluationDetails details = - client.getBooleanDetails("checkout.new", false, context); - -logger.info("Value: {}", details.getValue()); -logger.info("Variant: {}", details.getVariant()); -logger.info("Reason: {}", details.getReason()); -{{< /code-block >}} - -### String Flags - -{{< code-block lang="java" >}} -// Evaluate string flags (e.g., UI themes, API endpoints) -String theme = client.getStringValue("ui.theme", "light", context); - -String apiEndpoint = client.getStringValue( - "payment.api.endpoint", - "https://api.example.com/v1", - context -); -{{< /code-block >}} - -### Number Flags - -{{< code-block lang="java" >}} -// Integer flags (e.g., limits, quotas) -int maxRetries = client.getIntegerValue("retries.max", 3, context); - -// Double flags (e.g., thresholds, rates) -double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); -{{< /code-block >}} - -### Object Flags - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.Value; - -// Evaluate object/JSON flags for complex configuration -Value config = client.getObjectValue("ui.config", new Value(), context); - -// Access structured data -if (config.isStructure()) { - Value timeout = config.asStructure().getValue("timeout"); - Value endpoint = config.asStructure().getValue("endpoint"); -} -{{< /code-block >}} - -## Error Handling - -The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.ErrorCode; - -// Check evaluation details for errors -FlagEvaluationDetails details = - client.getBooleanDetails("checkout.new", false, context); - -if (details.getErrorCode() != null) { - switch (details.getErrorCode()) { - case FLAG_NOT_FOUND: - logger.warn("Flag does not exist: {}", "checkout.new"); - break; - case PROVIDER_NOT_READY: - logger.warn("Provider not initialized yet"); - break; - case TARGETING_KEY_MISSING: - logger.warn("Evaluation context missing targeting key"); - break; - case TYPE_MISMATCH: - logger.error("Flag value type doesn't match requested type"); - break; - default: - logger.error("Evaluation error for flag: {}", "checkout.new", details.getErrorCode()); - } -} -{{< /code-block >}} - -### Common Error Codes - -| Error Code | Description | Resolution | -|------------|-------------|------------| -| `PROVIDER_NOT_READY` | Initial configuration not received | Wait for provider initialization or use `setProviderAndWait()` | -| `FLAG_NOT_FOUND` | Flag doesn't exist in configuration | Check flag key or create flag in Datadog UI | -| `TARGETING_KEY_MISSING` | No targeting key in evaluation context | Provide a targeting key when creating context | -| `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | -| `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | - -## Exposure Tracking - -Flag exposures are automatically tracked and sent to Datadog when: -1. A flag is evaluated successfully -2. The flag's allocation has `doLog=true` configured - -Exposures appear in the Datadog UI and can be used for: -- Analyzing feature adoption -- Correlating feature flags with application performance -- Debugging flag behavior - -No additional code is required - exposures are automatically logged by the Datadog tracer integration. - -## Advanced Configuration - -### Custom Initialization Timeout - -Configure how long the provider waits for initial configuration: - -{{< code-block lang="java" >}} -import datadog.trace.api.openfeature.Provider; -import java.util.concurrent.TimeUnit; - -Provider.Options options = new Provider.Options() - .initTimeout(10, TimeUnit.SECONDS); - -api.setProviderAndWait(new Provider(options)); -{{< /code-block >}} - -### Configuration Change Events - -Listen for configuration updates from Remote Config: - -{{< code-block lang="java" >}} -import dev.openfeature.sdk.ProviderEvent; - -client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { - logger.info("Flag configuration updated: {}", event.getMessage()); - // Optionally re-evaluate flags or trigger cache refresh -}); -{{< /code-block >}} - -
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
- -### Multiple Clients - -Use named clients to organize context and flags by domain or team: - -{{< code-block lang="java" >}} -// Named clients share the same provider instance but can have different contexts -Client checkoutClient = api.getClient("checkout"); -Client analyticsClient = api.getClient("analytics"); - -// Each client can have its own evaluation context -EvaluationContext checkoutContext = new MutableContext("session-abc"); -EvaluationContext analyticsContext = new MutableContext("user-123"); - -boolean newCheckout = checkoutClient.getBooleanValue( - "checkout.ui.new", false, checkoutContext -); - -boolean enhancedAnalytics = analyticsClient.getBooleanValue( - "analytics.enhanced", false, analyticsContext -); -{{< /code-block >}} - -
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
- -## Best Practices - -### 1. Initialize Early -Initialize the OpenFeature provider as early as possible in your application lifecycle (e.g., in `main()` or application startup) to ensure flags are ready before business logic executes. - -### 2. Use Meaningful Default Values -Always provide sensible default values that maintain safe behavior if flag evaluation fails: - -{{< code-block lang="java" >}} -// Good: Safe default that maintains current behavior -boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context); - -// Good: Conservative default for limits -int rateLimit = client.getIntegerValue("rate.limit", 100, context); -{{< /code-block >}} - -### 3. Create Context Once -Create the evaluation context once per request/user/session and reuse it for all flag evaluations: - -{{< code-block lang="java" >}} -// In a web filter or request handler -EvaluationContext userContext = new MutableContext(userId) - .add("email", user.getEmail()) - .add("tier", user.getTier()); - -// Reuse context for all flags in this request -boolean featureA = client.getBooleanValue("feature.a", false, userContext); -boolean featureB = client.getBooleanValue("feature.b", false, userContext); -{{< /code-block >}} - -### 4. Handle Initialization Failures (Optional) -Consider handling initialization failures if your application can function with default flag values: - -{{< code-block lang="java" >}} -try { - api.setProviderAndWait(new Provider()); -} catch (ProviderNotReadyError e) { - // Log error and continue with defaults - logger.warn("Feature flags not ready, using defaults", e); - // Application will use default values for all flags -} -{{< /code-block >}} - -If feature flags are critical for your application to function, let the exception propagate to prevent startup. - -### 5. Use Consistent Targeting Keys -Use consistent, stable identifiers as targeting keys: -- **Good**: User IDs, session IDs, device IDs -- **Avoid**: Timestamps, random values, frequently changing IDs - -### 6. Monitor Flag Evaluation -Use the detailed evaluation results for logging and debugging: - -{{< code-block lang="java" >}} -FlagEvaluationDetails details = - client.getBooleanDetails("feature.critical", false, context); - -logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - "feature.critical", - details.getValue(), - details.getVariant(), - details.getReason() -); -{{< /code-block >}} - -## Integration with Datadog APM - -Feature flags automatically integrate with Datadog APM: - -- **Trace Correlation**: Flag evaluations are automatically correlated with APM traces -- **Performance Impact**: Track how feature flags affect application performance -- **Error Tracking**: See which flags were active when errors occurred -- **Exposure Analytics**: Analyze feature adoption in the Datadog UI - -No additional configuration is required - this integration is automatic when using the Datadog tracer. - -## Troubleshooting - -### Provider Not Ready - -**Problem**: `PROVIDER_NOT_READY` errors when evaluating flags - -**Common Causes**: -1. **Experimental flag not enabled**: Feature flagging is disabled by default -2. **Agent not ready**: Application started before Agent was fully initialized -3. **No flags configured**: No flags published to your service/environment combination -4. **Agent Remote Config disabled**: Agent not configured for Remote Configuration - -**Solutions**: -1. **Enable experimental feature**: - ```bash - export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true - ``` -2. **Verify Feature Flagging system started** in application logs: - ``` - [dd.trace] Feature Flagging system starting - [dd.trace] Feature Flagging system started - ``` -3. **Ensure Agent is ready** before app starts (use healthchecks in Docker/K8s) -4. **Check EVP Proxy discovered** in logs: - ``` - discovered ... evpProxyEndpoint=evp_proxy/v4/ configEndpoint=v0.7/config - ``` -5. **Wait for Remote Config sync** (can take 30-60 seconds after publishing flags) -6. **Verify flags are published** in Datadog UI to the correct service and environment - -### ClassNotFoundException or NoClassDefFoundError - -**Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` - -**Cause**: Missing the bootstrap JAR dependency - -**Solutions**: -1. **Add the bootstrap JAR** to your dependencies: - ```xml - - com.datadoghq - dd-java-agent-feature-flagging-bootstrap - X.X.X - - ``` -2. **Verify both dependencies are included** in your build: - - `dd-openfeature` (the OpenFeature provider) - - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) -3. **Check the classpath** includes both JARs in your runtime configuration - -
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
- -### Feature Flagging System Not Starting - -**Problem**: No "Feature Flagging system starting" messages in logs - -**Cause**: Experimental flag not enabled in tracer - -**Solution**: -Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` - -### EVP Proxy Not Available Error - -**Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" - -**Cause**: Application started before Agent was fully initialized - -**Solutions**: -1. **Add Agent healthcheck** in orchestration (Docker Compose, Kubernetes) -2. **Add startup delay** to application -3. **Retry logic**: Implement retry on provider initialization failure -4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support - -### Flags Not Updating - -**Problem**: Flag configuration changes aren't reflected in the application - -**Solutions**: -1. Check Remote Configuration is enabled on both Agent and application -2. Verify Agent can connect to Datadog backend -3. Check application logs for "No configuration changes" or "Configuration received" -4. Ensure flags are published (not just saved as draft) in the Datadog UI -5. Verify service and environment tags match between app and flag targeting - -### Type Mismatch Errors - -**Problem**: `TYPE_MISMATCH` errors when evaluating flags - -**Solutions**: -1. Verify the flag type in Datadog UI matches the evaluation method -2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` -3. Check flag configuration for correct value types - -### No Exposures in Datadog - -**Problem**: Flag evaluations aren't appearing in Datadog UI - -**Solutions**: -1. Verify the flag's allocation has `doLog=true` configured -2. Check Datadog Agent is receiving exposure events -3. Verify `DD_API_KEY` is correct -4. Check Agent logs for exposure upload errors - -## Further Reading - -{{< partial name="whats-next/whats-next.html" >}} diff --git a/content/en/feature_flags/setup/server/java.md b/content/en/feature_flags/setup/server/java.md index 0b8c544158e..9699e11dd7f 100644 --- a/content/en/feature_flags/setup/server/java.md +++ b/content/en/feature_flags/setup/server/java.md @@ -5,20 +5,694 @@ further_reading: - link: "/feature_flags/setup/server/" tag: "Documentation" text: "Server-Side Feature Flags" -- link: "/tracing/trace_collection/dd_libraries/java/" +- link: "/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/" tag: "Documentation" - text: "Java Tracing" + text: "Java APM and Distributed Tracing" --- {{< callout url="http://datadoghq.com/product-preview/feature-flags/" >}} Feature Flags are in Preview. Complete the form to request access. {{< /callout >}} +
Experimental Feature: Java Feature Flags support is experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
+ ## Overview -This page describes how to instrument your Java application with the Datadog Feature Flags SDK. +This page describes how to instrument a Java application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence. + +The Java SDK integrates feature flags directly into the Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. + +
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
+ +## Compatibility requirements + +The Datadog Feature Flags SDK for Java requires: +- **Java 11 or higher** +- **Datadog Java APM Tracer**: Version **1.57.0** or later +- **OpenFeature SDK**: Version **1.18.2** or later +- **Datadog Agent**: Version **7.x or later** with Remote Configuration enabled +- **Datadog API Key**: Required for Remote Configuration + +For a full list of Datadog's Java version and framework support, read [Compatibility Requirements](/tracing/trace_collection/compatibility/java/). + +## Getting started + +Before you begin, make sure you've already [installed and configured the Agent](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#install-and-configure-the-agent). + +## Installation + +Feature flagging is integrated into the Datadog Java APM tracer. You need the tracer JAR and the OpenFeature SDK dependencies. + +{{< tabs >}} +{{% tab "Gradle" %}} +Add the following dependencies to your `build.gradle`: + +{{< code-block lang="groovy" filename="build.gradle" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation 'com.datadoghq:dd-trace-api:X.X.X' + + // OpenFeature SDK for flag evaluation + implementation 'dev.openfeature:sdk:1.18.2' + + // Datadog OpenFeature Provider + implementation 'com.datadoghq:dd-openfeature:X.X.X' + + // Datadog Feature Flagging Bootstrap (required) + implementation 'com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X' +} +{{< /code-block >}} + +Or with Kotlin DSL (`build.gradle.kts`): + +{{< code-block lang="kotlin" filename="build.gradle.kts" >}} +dependencies { + // Datadog Java tracer (includes feature flagging) + implementation("com.datadoghq:dd-trace-api:X.X.X") + + // OpenFeature SDK for flag evaluation + implementation("dev.openfeature:sdk:1.18.2") + + // Datadog OpenFeature Provider + implementation("com.datadoghq:dd-openfeature:X.X.X") + + // Datadog Feature Flagging Bootstrap (required) + implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") +} +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Maven" %}} +Add the following dependencies to your `pom.xml`: + +{{< code-block lang="xml" filename="pom.xml" >}} + + + + com.datadoghq + dd-trace-api + X.X.X + + + + + dev.openfeature + sdk + 1.18.2 + + + + + com.datadoghq + dd-openfeature + X.X.X + + + + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + + +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +
What is the bootstrap JAR? The dd-java-agent-feature-flagging-bootstrap JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work.
+ +## Configuration + +
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
+ +### Agent configuration + +Configure your Datadog Agent to enable Remote Configuration: + +{{< code-block lang="yaml" filename="datadog.yaml" >}} +# Enable Remote Configuration +remote_configuration: + enabled: true + +# Set your API key +api_key: +{{< /code-block >}} + +### Application configuration + +
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
+ +Configure your Java application with the required environment variables or system properties: + +{{< tabs >}} +{{% tab "Environment Variables" %}} +{{< code-block lang="bash" >}} +# Required: Enable Remote Configuration in the tracer +export DD_REMOTE_CONFIG_ENABLED=true + +# Required: Enable experimental feature flagging support +export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + +# Required: Your Datadog API key +export DD_API_KEY= + +# Required: Service name +export DD_SERVICE= + +# Required: Environment (e.g., prod, staging, dev) +export DD_ENV= + +# Optional: Version +export DD_VERSION= + +# Start your application with the tracer +java -javaagent:path/to/dd-java-agent.jar -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} + +{{% tab "System Properties" %}} +{{< code-block lang="bash" >}} +java -javaagent:path/to/dd-java-agent.jar \ + -Ddd.remote.config.enabled=true \ + -Ddd.experimental.flagging.provider.enabled=true \ + -Ddd.api.key= \ + -Ddd.service= \ + -Ddd.env= \ + -Ddd.version= \ + -jar your-application.jar +{{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + +
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start.
+ +
Note: The Datadog feature flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+ +### Add the Java tracer to the JVM + +Use the documentation for your application server to determine the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: + +{{< tabs >}} +{{% tab "Spring Boot" %}} + +If your app is called `my_app.jar`, create a `my_app.conf`, containing: + +```text +JAVA_OPTS=-javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true +``` + +For more information, see the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-script-customization-when-it-runs). + +{{% /tab %}} +{{% tab "Tomcat" %}} + +Open your Tomcat startup script file, for example `setenv.sh`, and add: + +```text +CATALINA_OPTS="$CATALINA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "JBoss" %}} + +Add the following line to the end of `standalone.conf`: + +```text +JAVA_OPTS="$JAVA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" +``` + +{{% /tab %}} +{{% tab "Jetty" %}} + +Add the following to your `start.ini`: + +```text +--exec +-javaagent:/path/to/dd-java-agent.jar +-Ddd.remote.config.enabled=true +-Ddd.experimental.flagging.provider.enabled=true +``` + +{{% /tab %}} +{{% tab "Docker" %}} + +Update your Dockerfile to download and use the tracer: + +```dockerfile +FROM openjdk:17-jre + +# Download the Datadog Java tracer +ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar + +# Copy your application +COPY myapp.jar . + +# Run with feature flags enabled +CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", "-Ddd.experimental.flagging.provider.enabled=true", "-jar", "myapp.jar"] +``` + +{{% /tab %}} +{{< /tabs >}} + +## Initialize the OpenFeature provider + +Initialize the Datadog OpenFeature provider in your application startup code. The provider connects to the feature flagging system running in the Datadog tracer. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Client; +import datadog.trace.api.openfeature.Provider; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + private static final Logger logger = LoggerFactory.getLogger(App.class); + private static Client client; + + public static void main(String[] args) throws Exception { + // Initialize the Datadog provider + logger.info("Initializing Datadog OpenFeature Provider..."); + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + + try { + // Set provider and wait for initial configuration (recommended) + api.setProviderAndWait(new Provider()); + client = api.getClient("my-app"); + logger.info("OpenFeature provider initialized successfully"); + } catch (ProviderNotReadyError e) { + // Optional: Handle gracefully - app will use default flag values + logger.warn("Provider not ready (no tracer/configuration available), continuing with defaults", e); + client = api.getClient("my-app"); + logger.info("App will use default flag values until provider is ready"); + } + + // Your application code here + } +} +{{< /code-block >}} + +
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Configuration. This ensures flags are ready before the application starts serving traffic. The default timeout is 30 seconds.
+ +
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows the application to start with default flag values if Remote Configuration is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
+ +### Asynchronous initialization + +For non-blocking initialization, use `setProvider()` and listen for provider events: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +OpenFeatureAPI api = OpenFeatureAPI.getInstance(); +Client client = api.getClient(); + +// Listen for provider state changes +client.on(ProviderEvent.PROVIDER_READY, (event) -> { + logger.info("Feature flags ready!"); +}); + +client.on(ProviderEvent.PROVIDER_ERROR, (event) -> { + logger.error("Provider error: {}", event.getMessage()); +}); + +client.on(ProviderEvent.PROVIDER_STALE, (event) -> { + logger.warn("Provider configuration is stale"); +}); + +// Set provider asynchronously +api.setProvider(new Provider()); +{{< /code-block >}} + +## Set the evaluation context + +The evaluation context defines the subject (user, device, session) for flag evaluation. It determines which flag variations are returned based on targeting rules. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.MutableContext; + +// Create an evaluation context with a targeting key and attributes +EvaluationContext context = new MutableContext("user-123") + .add("email", "user@example.com") + .add("tier", "premium"); + +// Use the context for flag evaluations (see next section) +{{< /code-block >}} + +
Targeting Key: The targetingKey (for example, "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
+ +## Evaluate flags + +Evaluate feature flags using the OpenFeature client. All flag types are supported: Boolean, string, integer, double, and object. + +### Boolean flags + +{{< code-block lang="java" >}} +// Simple Boolean evaluation +boolean enabled = client.getBooleanValue("checkout.new", false, context); + +if (enabled) { + // New checkout flow +} else { + // Old checkout flow +} + +// Get detailed evaluation result +import dev.openfeature.sdk.FlagEvaluationDetails; + +FlagEvaluationDetails details = + client.getBooleanDetails("checkout.new", false, context); + +logger.info("Value: {}", details.getValue()); +logger.info("Variant: {}", details.getVariant()); +logger.info("Reason: {}", details.getReason()); +{{< /code-block >}} + +### String flags + +{{< code-block lang="java" >}} +// Evaluate string flags (e.g., UI themes, API endpoints) +String theme = client.getStringValue("ui.theme", "light", context); + +String apiEndpoint = client.getStringValue( + "payment.api.endpoint", + "https://api.example.com/v1", + context +); +{{< /code-block >}} + +### Number flags + +{{< code-block lang="java" >}} +// Integer flags (e.g., limits, quotas) +int maxRetries = client.getIntegerValue("retries.max", 3, context); + +// Double flags (e.g., thresholds, rates) +double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); +{{< /code-block >}} + +### Object flags + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.Value; + +// Evaluate object/JSON flags for complex configuration +Value config = client.getObjectValue("ui.config", new Value(), context); + +// Access structured data +if (config.isStructure()) { + Value timeout = config.asStructure().getValue("timeout"); + Value endpoint = config.asStructure().getValue("endpoint"); +} +{{< /code-block >}} + +## Error handling + +The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ErrorCode; + +// Check evaluation details for errors +FlagEvaluationDetails details = + client.getBooleanDetails("checkout.new", false, context); + +if (details.getErrorCode() != null) { + switch (details.getErrorCode()) { + case FLAG_NOT_FOUND: + logger.warn("Flag does not exist: {}", "checkout.new"); + break; + case PROVIDER_NOT_READY: + logger.warn("Provider not initialized yet"); + break; + case TARGETING_KEY_MISSING: + logger.warn("Evaluation context missing targeting key"); + break; + case TYPE_MISMATCH: + logger.error("Flag value type doesn't match requested type"); + break; + default: + logger.error("Evaluation error for flag: {}", "checkout.new", details.getErrorCode()); + } +} +{{< /code-block >}} + +### Common error codes + +| Error Code | Description | Resolution | +|------------|-------------|------------| +| `PROVIDER_NOT_READY` | Initial configuration not received | Wait for provider initialization or use `setProviderAndWait()` | +| `FLAG_NOT_FOUND` | Flag doesn't exist in configuration | Check flag key or create flag in Datadog UI | +| `TARGETING_KEY_MISSING` | No targeting key in evaluation context | Provide a targeting key when creating context | +| `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | +| `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | + +## Exposure tracking + +Flag exposures are automatically tracked and sent to Datadog when: +1. A flag is evaluated successfully +2. The flag's allocation has `doLog=true` configured + +Exposures appear in the Datadog UI and can be used for: +- Analyzing feature adoption +- Correlating feature flags with application performance +- Debugging flag behavior + +No additional code is required - exposures are automatically logged by the Datadog tracer integration. + +## Advanced configuration + +### Custom initialization timeout + +Configure how long the provider waits for initial configuration: + +{{< code-block lang="java" >}} +import datadog.trace.api.openfeature.Provider; +import java.util.concurrent.TimeUnit; + +Provider.Options options = new Provider.Options() + .initTimeout(10, TimeUnit.SECONDS); + +api.setProviderAndWait(new Provider(options)); +{{< /code-block >}} + +### Configuration change events + +Listen for configuration updates from Remote Configuration: + +{{< code-block lang="java" >}} +import dev.openfeature.sdk.ProviderEvent; + +client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { + logger.info("Flag configuration updated: {}", event.getMessage()); + // Optionally re-evaluate flags or trigger cache refresh +}); +{{< /code-block >}} + +
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
+ +### Multiple clients + +Use named clients to organize context and flags by domain or team: + +{{< code-block lang="java" >}} +// Named clients share the same provider instance but can have different contexts +Client checkoutClient = api.getClient("checkout"); +Client analyticsClient = api.getClient("analytics"); + +// Each client can have its own evaluation context +EvaluationContext checkoutContext = new MutableContext("session-abc"); +EvaluationContext analyticsContext = new MutableContext("user-123"); + +boolean newCheckout = checkoutClient.getBooleanValue( + "checkout.ui.new", false, checkoutContext +); + +boolean enhancedAnalytics = analyticsClient.getBooleanValue( + "analytics.enhanced", false, analyticsContext +); +{{< /code-block >}} + +
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
+ +## Best practices + +### 1. Initialize early +Initialize the OpenFeature provider as early as possible in your application lifecycle (for example, in `main()` or application startup). This ensures flags are ready before business logic executes. + +### 2. Use meaningful default values +Always provide sensible default values that maintain safe behavior if flag evaluation fails: + +{{< code-block lang="java" >}} +// Good: Safe default that maintains current behavior +boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context); + +// Good: Conservative default for limits +int rateLimit = client.getIntegerValue("rate.limit", 100, context); +{{< /code-block >}} + +### 3. Create context once +Create the evaluation context once per request/user/session and reuse it for all flag evaluations: + +{{< code-block lang="java" >}} +// In a web filter or request handler +EvaluationContext userContext = new MutableContext(userId) + .add("email", user.getEmail()) + .add("tier", user.getTier()); + +// Reuse context for all flags in this request +boolean featureA = client.getBooleanValue("feature.a", false, userContext); +boolean featureB = client.getBooleanValue("feature.b", false, userContext); +{{< /code-block >}} + +### 4. Handle initialization failures (optional) +Consider handling initialization failures if your application can function with default flag values: + +{{< code-block lang="java" >}} +try { + api.setProviderAndWait(new Provider()); +} catch (ProviderNotReadyError e) { + // Log error and continue with defaults + logger.warn("Feature flags not ready, using defaults", e); + // Application will use default values for all flags +} +{{< /code-block >}} + +If feature flags are critical for your application to function, let the exception propagate to prevent startup. + +### 5. Use consistent targeting keys +Use consistent, stable identifiers as targeting keys: +- **Good**: User IDs, session IDs, device IDs +- **Avoid**: Timestamps, random values, frequently changing IDs + +### 6. Monitor flag evaluation +Use the detailed evaluation results for logging and debugging: + +{{< code-block lang="java" >}} +FlagEvaluationDetails details = + client.getBooleanDetails("feature.critical", false, context); + +logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", + "feature.critical", + details.getValue(), + details.getVariant(), + details.getReason() +); +{{< /code-block >}} + +## Integration with Datadog APM + +Feature flags automatically integrate with Datadog APM: + +- **Trace Correlation**: Flag evaluations are automatically correlated with APM traces +- **Performance Impact**: Track how feature flags affect application performance +- **Error Tracking**: See which flags were active when errors occurred +- **Exposure Analytics**: Analyze feature adoption in the Datadog UI + +No additional configuration is required - this integration is automatic when using the Datadog tracer. + +## Troubleshooting + +### Provider not ready + +**Problem**: `PROVIDER_NOT_READY` errors when evaluating flags + +**Common Causes**: +1. **Experimental flag not enabled**: Feature flagging is disabled by default +2. **Agent not ready**: Application started before Agent was fully initialized +3. **No flags configured**: No flags published to your service/environment combination +4. **Agent Remote Configuration disabled**: Agent not configured for Remote Configuration + +**Solutions**: +1. **Enable experimental feature**: + ```bash + export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true + ``` +2. **Verify feature flagging system started** in application logs: + ``` + [dd.trace] Feature Flagging system starting + [dd.trace] Feature Flagging system started + ``` +3. **Ensure Agent is ready** before app starts (use health checks in Docker/Kubernetes) +4. **Check EVP Proxy discovered** in logs: + ``` + discovered ... evpProxyEndpoint=evp_proxy/v4/ configEndpoint=v0.7/config + ``` +5. **Wait for Remote Configuration sync** (can take 30-60 seconds after publishing flags) +6. **Verify flags are published** in Datadog UI to the correct service and environment + +### ClassNotFoundException or NoClassDefFoundError + +**Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` + +**Cause**: Missing the bootstrap JAR dependency + +**Solutions**: +1. **Add the bootstrap JAR** to your dependencies: + ```xml + + com.datadoghq + dd-java-agent-feature-flagging-bootstrap + X.X.X + + ``` +2. **Verify both dependencies are included** in your build: + - `dd-openfeature` (the OpenFeature provider) + - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) +3. **Check the classpath** includes both JARs in your runtime configuration + +
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
+ +### Feature flagging system not starting + +**Problem**: No "Feature Flagging system starting" messages in logs + +**Cause**: Experimental flag not enabled in tracer + +**Solution**: +Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` + +### EVP proxy not available error + +**Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" + +**Cause**: Application started before Agent was fully initialized + +**Solutions**: +1. **Add Agent health check** in orchestration (Docker Compose, Kubernetes) +2. **Add startup delay** to application +3. **Retry logic**: Implement retry on provider initialization failure +4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support + +### Flags not updating + +**Problem**: Flag configuration changes aren't reflected in the application + +**Solutions**: +1. Check Remote Configuration is enabled on both Agent and application +2. Verify Agent can connect to Datadog backend +3. Check application logs for "No configuration changes" or "Configuration received" +4. Ensure flags are published (not saved as drafts) in the Datadog UI +5. Verify service and environment tags match between app and flag targeting + +### Type mismatch errors + +**Problem**: `TYPE_MISMATCH` errors when evaluating flags + +**Solutions**: +1. Verify the flag type in Datadog UI matches the evaluation method +2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` +3. Check flag configuration for correct value types + +### No exposures in Datadog + +**Problem**: Flag evaluations aren't appearing in Datadog UI -Documentation coming soon. +**Solutions**: +1. Verify the flag's allocation has `doLog=true` configured +2. Check Datadog Agent is receiving exposure events +3. Verify `DD_API_KEY` is correct +4. Check Agent logs for exposure upload errors ## Further reading From b90eee716779d4839c2d4a3baa2557b7b68fe7f3 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Tue, 9 Dec 2025 08:52:50 -0700 Subject: [PATCH 15/19] extra dev info --- content/en/feature_flags/setup/server/java.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/content/en/feature_flags/setup/server/java.md b/content/en/feature_flags/setup/server/java.md index 9699e11dd7f..e116f85440d 100644 --- a/content/en/feature_flags/setup/server/java.md +++ b/content/en/feature_flags/setup/server/java.md @@ -184,7 +184,7 @@ java -javaagent:path/to/dd-java-agent.jar \ {{% /tab %}} {{< /tabs >}} -
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start.
+
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start and the `Provider` will return the programmatic default.
Note: The Datadog feature flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
@@ -545,6 +545,10 @@ boolean featureA = client.getBooleanValue("feature.a", false, userContext); boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} +{{< callout type="info" >}} +Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations. +{{< /callout >}} + ### 4. Handle initialization failures (optional) Consider handling initialization failures if your application can function with default flag values: @@ -580,17 +584,6 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", ); {{< /code-block >}} -## Integration with Datadog APM - -Feature flags automatically integrate with Datadog APM: - -- **Trace Correlation**: Flag evaluations are automatically correlated with APM traces -- **Performance Impact**: Track how feature flags affect application performance -- **Error Tracking**: See which flags were active when errors occurred -- **Exposure Analytics**: Analyze feature adoption in the Datadog UI - -No additional configuration is required - this integration is automatic when using the Datadog tracer. - ## Troubleshooting ### Provider not ready From e828bad0466a065e239c01fc62639a24cdf268b7 Mon Sep 17 00:00:00 2001 From: Joe Peeples Date: Tue, 9 Dec 2025 16:07:50 -0500 Subject: [PATCH 16/19] various style edits --- content/en/feature_flags/server/java.md | 51 ++++++++++++------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/content/en/feature_flags/server/java.md b/content/en/feature_flags/server/java.md index 70a489d47a0..8387ecbde18 100644 --- a/content/en/feature_flags/server/java.md +++ b/content/en/feature_flags/server/java.md @@ -14,7 +14,7 @@ further_reading: Feature Flags are in Preview. Complete the form to request access. {{< /callout >}} -
Experimental Feature: Java Feature Flags support is experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
+
Java Feature Flags support is experimental and requires enabling an experimental flag in the tracer. See the Configuration section for details.
## Overview @@ -22,7 +22,7 @@ This page describes how to instrument a Java application with the Datadog Featur The Java SDK integrates feature flags directly into the Datadog APM tracer and implements the [OpenFeature](https://openfeature.dev/) standard for maximum flexibility and compatibility. -
Already using Datadog APM? If your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature Provider. You only need to add the OpenFeature dependencies and initialize the provider.
+
If you're using Datadog APM and your application already has the Datadog Java tracer and Remote Configuration enabled, skip to Initialize the OpenFeature provider. You only need to add the OpenFeature dependencies and initialize the provider.
## Compatibility requirements @@ -43,6 +43,8 @@ Before you begin, make sure you've already [installed and configured the Agent]( Feature flagging is integrated into the Datadog Java APM tracer. You need the tracer JAR and the OpenFeature SDK dependencies. +The `dd-java-agent-feature-flagging-bootstrap` JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work. + {{< tabs >}} {{% tab "Gradle" %}} Add the following dependencies to your `build.gradle`: @@ -119,11 +121,9 @@ Add the following dependencies to your `pom.xml`: {{% /tab %}} {{< /tabs >}} -
What is the bootstrap JAR? The dd-java-agent-feature-flagging-bootstrap JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work.
- ## Configuration -
Already using Remote Configuration? If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent Configuration section and go directly to Application Configuration.
+If your Datadog Agent already has Remote Configuration enabled for other features (like Dynamic Instrumentation or Application Security), you can skip the Agent configuration and go directly to [Application configuration](#application-configuration). ### Agent configuration @@ -140,7 +140,7 @@ api_key: ### Application configuration -
Already using the Datadog Java tracer? If your application already runs with -javaagent:dd-java-agent.jar and has Remote Configuration enabled (DD_REMOTE_CONFIG_ENABLED=true), you only need to add the experimental feature flag (DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true). Skip the tracer download and JVM configuration steps.
+If your application already runs with `-javaagent:dd-java-agent.jar` and has Remote Configuration enabled (`DD_REMOTE_CONFIG_ENABLED=true`), you only need to add the experimental feature flag (`DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true`). Skip the tracer download and JVM configuration steps. Configure your Java application with the required environment variables or system properties: @@ -184,9 +184,9 @@ java -javaagent:path/to/dd-java-agent.jar \ {{% /tab %}} {{< /tabs >}} -
Important: Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start and the `Provider` will return the programmatic default.
+The Datadog feature flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application. -
Note: The Datadog feature flagging system starts automatically when the tracer is initialized with both Remote Configuration and the experimental flagging provider enabled. No additional initialization code is required in your application.
+
Feature flagging requires both DD_REMOTE_CONFIG_ENABLED=true and DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. Without the experimental flag, the feature flagging system does not start and the Provider returns the programmatic default.
### Add the Java tracer to the JVM @@ -292,9 +292,9 @@ public class App { } {{< /code-block >}} -
Important: Use setProviderAndWait() to block until the initial flag configuration is received from Remote Configuration. This ensures flags are ready before the application starts serving traffic. The default timeout is 30 seconds.
+Use `setProviderAndWait()` to block until the initial flag configuration is received from Remote Configuration. This ensures flags are ready before the application starts serving traffic. The default timeout is 30 seconds. -
Exception Handling (Optional): ProviderNotReadyError is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows the application to start with default flag values if Remote Configuration is unavailable. If not caught, the exception propagates and may prevent application startup—handle this based on your availability requirements.
+`ProviderNotReadyError` is an OpenFeature SDK exception thrown when the provider times out during initialization. Catching it allows the application to start with default flag values if Remote Configuration is unavailable. If not caught, the exception propagates and may prevent application startup. Handle this based on your availability requirements. ### Asynchronous initialization @@ -339,7 +339,7 @@ EvaluationContext context = new MutableContext("user-123") // Use the context for flag evaluations (see next section) {{< /code-block >}} -
Targeting Key: The targetingKey (for example, "user-123") is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID.
+The `targetingKey` (for example, `user-123`) is the primary identifier used for consistent flag evaluations and percentage-based rollouts. It's typically a user ID, session ID, or device ID. ## Evaluate flags @@ -408,7 +408,7 @@ if (config.isStructure()) { ## Error handling -The OpenFeature SDK uses a default value pattern - if evaluation fails for any reason, the default value you provide is returned. +The OpenFeature SDK uses a default value pattern. If evaluation fails for any reason, the default value you provide is returned. {{< code-block lang="java" >}} import dev.openfeature.sdk.ErrorCode; @@ -458,7 +458,7 @@ Exposures appear in the Datadog UI and can be used for: - Correlating feature flags with application performance - Debugging flag behavior -No additional code is required - exposures are automatically logged by the Datadog tracer integration. +No additional code is required. Exposures are automatically logged by the Datadog tracer integration. ## Advanced configuration @@ -489,7 +489,7 @@ client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, (event) -> { }); {{< /code-block >}} -
Note: PROVIDER_CONFIGURATION_CHANGED is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version.
+`PROVIDER_CONFIGURATION_CHANGED` is an optional OpenFeature event. Check the Datadog provider documentation to verify this event is supported in your version. ### Multiple clients @@ -513,14 +513,14 @@ boolean enhancedAnalytics = analyticsClient.getBooleanValue( ); {{< /code-block >}} -
Note: The Provider instance is shared globally—client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations.
+The `Provider` instance is shared globally. Client names are for organizational purposes only and don't create separate provider instances. All clients use the same underlying Datadog provider and flag configurations. ## Best practices -### 1. Initialize early +### Initialize early Initialize the OpenFeature provider as early as possible in your application lifecycle (for example, in `main()` or application startup). This ensures flags are ready before business logic executes. -### 2. Use meaningful default values +### Use meaningful default values Always provide sensible default values that maintain safe behavior if flag evaluation fails: {{< code-block lang="java" >}} @@ -531,7 +531,7 @@ boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} -### 3. Create context once +### Create context once Create the evaluation context once per request/user/session and reuse it for all flag evaluations: {{< code-block lang="java" >}} @@ -545,11 +545,9 @@ boolean featureA = client.getBooleanValue("feature.a", false, userContext); boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} -{{< callout type="info" >}} -Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations. -{{< /callout >}} +
Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations.
-### 4. Handle initialization failures (optional) +### Handle initialization failures (optional) Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} @@ -564,12 +562,12 @@ try { If feature flags are critical for your application to function, let the exception propagate to prevent startup. -### 5. Use consistent targeting keys +### Use consistent targeting keys Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs - **Avoid**: Timestamps, random values, frequently changing IDs -### 6. Monitor flag evaluation +### Monitor flag evaluation Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} @@ -618,7 +616,8 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", **Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` -**Cause**: Missing the bootstrap JAR dependency +**Cause**: Missing the bootstrap JAR dependency. The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact. + **Solutions**: 1. **Add the bootstrap JAR** to your dependencies: @@ -634,8 +633,6 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) 3. **Check the classpath** includes both JARs in your runtime configuration -
Why the bootstrap JAR is needed: The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact.
- ### Feature flagging system not starting **Problem**: No "Feature Flagging system starting" messages in logs From 113ccbf6654e00d6b7868206b949acbe8e430147 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Wed, 10 Dec 2025 08:57:14 -0700 Subject: [PATCH 17/19] section collapsing --- content/en/feature_flags/server/java.md | 52 ++++++++++++++++++------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/content/en/feature_flags/server/java.md b/content/en/feature_flags/server/java.md index 8387ecbde18..ad3c0f1b195 100644 --- a/content/en/feature_flags/server/java.md +++ b/content/en/feature_flags/server/java.md @@ -47,6 +47,9 @@ The `dd-java-agent-feature-flagging-bootstrap` JAR contains shared interfaces th {{< tabs >}} {{% tab "Gradle" %}} + +{{< tabs >}} +{{% tab "Groovy" %}} Add the following dependencies to your `build.gradle`: {{< code-block lang="groovy" filename="build.gradle" >}} @@ -64,8 +67,10 @@ dependencies { implementation 'com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X' } {{< /code-block >}} +{{% /tab %}} -Or with Kotlin DSL (`build.gradle.kts`): +{{% tab "Kotlin" %}} +Add the following dependencies to your `build.gradle.kts`: {{< code-block lang="kotlin" filename="build.gradle.kts" >}} dependencies { @@ -82,6 +87,9 @@ dependencies { implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") } {{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} + {{% /tab %}} {{% tab "Maven" %}} @@ -368,8 +376,10 @@ logger.info("Variant: {}", details.getVariant()); logger.info("Reason: {}", details.getReason()); {{< /code-block >}} -### String flags +### Other flag types +{{< tabs >}} +{{% tab "String" %}} {{< code-block lang="java" >}} // Evaluate string flags (e.g., UI themes, API endpoints) String theme = client.getStringValue("ui.theme", "light", context); @@ -380,9 +390,9 @@ String apiEndpoint = client.getStringValue( context ); {{< /code-block >}} +{{% /tab %}} -### Number flags - +{{% tab "Number" %}} {{< code-block lang="java" >}} // Integer flags (e.g., limits, quotas) int maxRetries = client.getIntegerValue("retries.max", 3, context); @@ -390,9 +400,9 @@ int maxRetries = client.getIntegerValue("retries.max", 3, context); // Double flags (e.g., thresholds, rates) double discountRate = client.getDoubleValue("pricing.discount.rate", 0.0, context); {{< /code-block >}} +{{% /tab %}} -### Object flags - +{{% tab "Object" %}} {{< code-block lang="java" >}} import dev.openfeature.sdk.Value; @@ -405,6 +415,8 @@ if (config.isStructure()) { Value endpoint = config.asStructure().getValue("endpoint"); } {{< /code-block >}} +{{% /tab %}} +{{< /tabs >}} ## Error handling @@ -584,7 +596,7 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", ## Troubleshooting -### Provider not ready +{{% collapse-content title="Provider not ready" level="h3" %}} **Problem**: `PROVIDER_NOT_READY` errors when evaluating flags @@ -612,7 +624,9 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", 5. **Wait for Remote Configuration sync** (can take 30-60 seconds after publishing flags) 6. **Verify flags are published** in Datadog UI to the correct service and environment -### ClassNotFoundException or NoClassDefFoundError +{{% /collapse-content %}} + +{{% collapse-content title="ClassNotFoundException or NoClassDefFoundError" level="h3" %}} **Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` @@ -633,7 +647,9 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) 3. **Check the classpath** includes both JARs in your runtime configuration -### Feature flagging system not starting +{{% /collapse-content %}} + +{{% collapse-content title="Feature flagging system not starting" level="h3" %}} **Problem**: No "Feature Flagging system starting" messages in logs @@ -642,7 +658,9 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", **Solution**: Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` -### EVP proxy not available error +{{% /collapse-content %}} + +{{% collapse-content title="EVP proxy not available error" level="h3" %}} **Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" @@ -654,7 +672,9 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. **Retry logic**: Implement retry on provider initialization failure 4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support -### Flags not updating +{{% /collapse-content %}} + +{{% collapse-content title="Flags not updating" level="h3" %}} **Problem**: Flag configuration changes aren't reflected in the application @@ -665,7 +685,9 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 4. Ensure flags are published (not saved as drafts) in the Datadog UI 5. Verify service and environment tags match between app and flag targeting -### Type mismatch errors +{{% /collapse-content %}} + +{{% collapse-content title="Type mismatch errors" level="h3" %}} **Problem**: `TYPE_MISMATCH` errors when evaluating flags @@ -674,7 +696,9 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` 3. Check flag configuration for correct value types -### No exposures in Datadog +{{% /collapse-content %}} + +{{% collapse-content title="No exposures in Datadog" level="h3" %}} **Problem**: Flag evaluations aren't appearing in Datadog UI @@ -684,6 +708,8 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. Verify `DD_API_KEY` is correct 4. Check Agent logs for exposure upload errors +{{% /collapse-content %}} + ## Further reading {{< partial name="whats-next/whats-next.html" >}} From 768466bda1041ed482eddbe573c878631d829e20 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Wed, 10 Dec 2025 10:37:18 -0700 Subject: [PATCH 18/19] better sections --- content/en/feature_flags/server/java.md | 67 ++++++++++--------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/content/en/feature_flags/server/java.md b/content/en/feature_flags/server/java.md index ad3c0f1b195..37f4be1cb69 100644 --- a/content/en/feature_flags/server/java.md +++ b/content/en/feature_flags/server/java.md @@ -46,10 +46,7 @@ Feature flagging is integrated into the Datadog Java APM tracer. You need the tr The `dd-java-agent-feature-flagging-bootstrap` JAR contains shared interfaces that enable the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). This is a standard pattern for Java agents. Both JARs are required for feature flags to work. {{< tabs >}} -{{% tab "Gradle" %}} - -{{< tabs >}} -{{% tab "Groovy" %}} +{{% tab "Gradle (Groovy)" %}} Add the following dependencies to your `build.gradle`: {{< code-block lang="groovy" filename="build.gradle" >}} @@ -69,7 +66,7 @@ dependencies { {{< /code-block >}} {{% /tab %}} -{{% tab "Kotlin" %}} +{{% tab "Gradle (Kotlin)" %}} Add the following dependencies to your `build.gradle.kts`: {{< code-block lang="kotlin" filename="build.gradle.kts" >}} @@ -87,9 +84,6 @@ dependencies { implementation("com.datadoghq:dd-java-agent-feature-flagging-bootstrap:X.X.X") } {{< /code-block >}} -{{% /tab %}} -{{< /tabs >}} - {{% /tab %}} {{% tab "Maven" %}} @@ -353,8 +347,8 @@ The `targetingKey` (for example, `user-123`) is the primary identifier used for Evaluate feature flags using the OpenFeature client. All flag types are supported: Boolean, string, integer, double, and object. -### Boolean flags - +{{< tabs >}} +{{% tab "Boolean" %}} {{< code-block lang="java" >}} // Simple Boolean evaluation boolean enabled = client.getBooleanValue("checkout.new", false, context); @@ -375,10 +369,8 @@ logger.info("Value: {}", details.getValue()); logger.info("Variant: {}", details.getVariant()); logger.info("Reason: {}", details.getReason()); {{< /code-block >}} +{{% /tab %}} -### Other flag types - -{{< tabs >}} {{% tab "String" %}} {{< code-block lang="java" >}} // Evaluate string flags (e.g., UI themes, API endpoints) @@ -529,10 +521,11 @@ The `Provider` instance is shared globally. Client names are for organizational ## Best practices -### Initialize early +{{% collapse-content title="Initialize early" level="p" %}} Initialize the OpenFeature provider as early as possible in your application lifecycle (for example, in `main()` or application startup). This ensures flags are ready before business logic executes. +{{% /collapse-content %}} -### Use meaningful default values +{{% collapse-content title="Use meaningful default values" level="p" %}} Always provide sensible default values that maintain safe behavior if flag evaluation fails: {{< code-block lang="java" >}} @@ -542,8 +535,9 @@ boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context // Good: Conservative default for limits int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} +{{% /collapse-content %}} -### Create context once +{{% collapse-content title="Create context once" level="p" %}} Create the evaluation context once per request/user/session and reuse it for all flag evaluations: {{< code-block lang="java" >}} @@ -557,9 +551,10 @@ boolean featureA = client.getBooleanValue("feature.a", false, userContext); boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} -
Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations.
+Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations. +{{% /collapse-content %}} -### Handle initialization failures (optional) +{{% collapse-content title="Handle initialization failures (optional)" level="p" %}} Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} @@ -573,13 +568,15 @@ try { {{< /code-block >}} If feature flags are critical for your application to function, let the exception propagate to prevent startup. +{{% /collapse-content %}} -### Use consistent targeting keys +{{% collapse-content title="Use consistent targeting keys" level="p" %}} Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs - **Avoid**: Timestamps, random values, frequently changing IDs +{{% /collapse-content %}} -### Monitor flag evaluation +{{% collapse-content title="Monitor flag evaluation" level="p" %}} Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} @@ -593,10 +590,13 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", details.getReason() ); {{< /code-block >}} +{{% /collapse-content %}} ## Troubleshooting -{{% collapse-content title="Provider not ready" level="h3" %}} +{{% collapse-content title="Troubleshooting" level="p" %}} + +### Provider not ready **Problem**: `PROVIDER_NOT_READY` errors when evaluating flags @@ -624,15 +624,12 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", 5. **Wait for Remote Configuration sync** (can take 30-60 seconds after publishing flags) 6. **Verify flags are published** in Datadog UI to the correct service and environment -{{% /collapse-content %}} - -{{% collapse-content title="ClassNotFoundException or NoClassDefFoundError" level="h3" %}} +### ClassNotFoundException or NoClassDefFoundError **Problem**: Application fails to start with `ClassNotFoundException` for Datadog classes like `datadog.trace.api.featureflag.FeatureFlaggingGateway` **Cause**: Missing the bootstrap JAR dependency. The bootstrap module contains shared interfaces that allow the Datadog tracer (running in the bootstrap classloader) to communicate with the OpenFeature provider (running in the application classloader). Without it, the two components cannot interact. - **Solutions**: 1. **Add the bootstrap JAR** to your dependencies: ```xml @@ -647,9 +644,7 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", - `dd-java-agent-feature-flagging-bootstrap` (the bootstrap module) 3. **Check the classpath** includes both JARs in your runtime configuration -{{% /collapse-content %}} - -{{% collapse-content title="Feature flagging system not starting" level="h3" %}} +### Feature flagging system not starting **Problem**: No "Feature Flagging system starting" messages in logs @@ -658,9 +653,7 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", **Solution**: Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or set `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true` -{{% /collapse-content %}} - -{{% collapse-content title="EVP proxy not available error" level="h3" %}} +### EVP proxy not available error **Problem**: Logs show "EVP Proxy not available" or "agent does not support EVP proxy" @@ -672,9 +665,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. **Retry logic**: Implement retry on provider initialization failure 4. **Upgrade Agent**: Ensure using Agent 7.x or later with EVP Proxy support -{{% /collapse-content %}} - -{{% collapse-content title="Flags not updating" level="h3" %}} +### Flags not updating **Problem**: Flag configuration changes aren't reflected in the application @@ -685,9 +676,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 4. Ensure flags are published (not saved as drafts) in the Datadog UI 5. Verify service and environment tags match between app and flag targeting -{{% /collapse-content %}} - -{{% collapse-content title="Type mismatch errors" level="h3" %}} +### Type mismatch errors **Problem**: `TYPE_MISMATCH` errors when evaluating flags @@ -696,9 +685,7 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 2. Use correct method: `getBooleanValue()`, `getStringValue()`, `getIntegerValue()`, `getDoubleValue()` 3. Check flag configuration for correct value types -{{% /collapse-content %}} - -{{% collapse-content title="No exposures in Datadog" level="h3" %}} +### No exposures in Datadog **Problem**: Flag evaluations aren't appearing in Datadog UI From a47a3810360501d14bc3a10ded0fc5e2f1ef35c5 Mon Sep 17 00:00:00 2001 From: Tyler Potter Date: Wed, 10 Dec 2025 11:50:26 -0700 Subject: [PATCH 19/19] reduce expanders --- content/en/feature_flags/server/java.md | 101 +++--------------------- 1 file changed, 10 insertions(+), 91 deletions(-) diff --git a/content/en/feature_flags/server/java.md b/content/en/feature_flags/server/java.md index 37f4be1cb69..6f35c688496 100644 --- a/content/en/feature_flags/server/java.md +++ b/content/en/feature_flags/server/java.md @@ -192,69 +192,11 @@ The Datadog feature flagging system starts automatically when the tracer is init ### Add the Java tracer to the JVM -Use the documentation for your application server to determine the right way to pass in `-javaagent` and other JVM arguments. Here are instructions for some commonly used frameworks: +For instructions on how to add the `-javaagent` argument to your application server or framework, see [Add the Java Tracer to the JVM](/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/#add-the-java-tracer-to-the-jvm). -{{< tabs >}} -{{% tab "Spring Boot" %}} - -If your app is called `my_app.jar`, create a `my_app.conf`, containing: - -```text -JAVA_OPTS=-javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true -``` - -For more information, see the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-script-customization-when-it-runs). - -{{% /tab %}} -{{% tab "Tomcat" %}} - -Open your Tomcat startup script file, for example `setenv.sh`, and add: - -```text -CATALINA_OPTS="$CATALINA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" -``` - -{{% /tab %}} -{{% tab "JBoss" %}} - -Add the following line to the end of `standalone.conf`: - -```text -JAVA_OPTS="$JAVA_OPTS -javaagent:/path/to/dd-java-agent.jar -Ddd.remote.config.enabled=true -Ddd.experimental.flagging.provider.enabled=true" -``` - -{{% /tab %}} -{{% tab "Jetty" %}} - -Add the following to your `start.ini`: - -```text ---exec --javaagent:/path/to/dd-java-agent.jar --Ddd.remote.config.enabled=true --Ddd.experimental.flagging.provider.enabled=true -``` - -{{% /tab %}} -{{% tab "Docker" %}} - -Update your Dockerfile to download and use the tracer: - -```dockerfile -FROM openjdk:17-jre - -# Download the Datadog Java tracer -ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar - -# Copy your application -COPY myapp.jar . - -# Run with feature flags enabled -CMD ["java", "-javaagent:dd-java-agent.jar", "-Ddd.remote.config.enabled=true", "-Ddd.experimental.flagging.provider.enabled=true", "-jar", "myapp.jar"] -``` - -{{% /tab %}} -{{< /tabs >}} +Make sure to include the feature flagging configuration flags: +- `-Ddd.remote.config.enabled=true` +- `-Ddd.experimental.flagging.provider.enabled=true` ## Initialize the OpenFeature provider @@ -451,19 +393,6 @@ if (details.getErrorCode() != null) { | `TYPE_MISMATCH` | Flag value can't be converted to requested type | Use correct evaluation method for flag type | | `INVALID_CONTEXT` | Evaluation context is null | Provide a valid evaluation context | -## Exposure tracking - -Flag exposures are automatically tracked and sent to Datadog when: -1. A flag is evaluated successfully -2. The flag's allocation has `doLog=true` configured - -Exposures appear in the Datadog UI and can be used for: -- Analyzing feature adoption -- Correlating feature flags with application performance -- Debugging flag behavior - -No additional code is required. Exposures are automatically logged by the Datadog tracer integration. - ## Advanced configuration ### Custom initialization timeout @@ -521,11 +450,10 @@ The `Provider` instance is shared globally. Client names are for organizational ## Best practices -{{% collapse-content title="Initialize early" level="p" %}} +### Initialize early Initialize the OpenFeature provider as early as possible in your application lifecycle (for example, in `main()` or application startup). This ensures flags are ready before business logic executes. -{{% /collapse-content %}} -{{% collapse-content title="Use meaningful default values" level="p" %}} +### Use meaningful default values Always provide sensible default values that maintain safe behavior if flag evaluation fails: {{< code-block lang="java" >}} @@ -535,9 +463,8 @@ boolean useNewAlgorithm = client.getBooleanValue("algorithm.new", false, context // Good: Conservative default for limits int rateLimit = client.getIntegerValue("rate.limit", 100, context); {{< /code-block >}} -{{% /collapse-content %}} -{{% collapse-content title="Create context once" level="p" %}} +### Create context once Create the evaluation context once per request/user/session and reuse it for all flag evaluations: {{< code-block lang="java" >}} @@ -552,9 +479,8 @@ boolean featureB = client.getBooleanValue("feature.b", false, userContext); {{< /code-block >}} Rebuilding the evaluation context for every flag evaluation adds unnecessary overhead. Create the context once at the start of the request lifecycle, then pass it to all subsequent flag evaluations. -{{% /collapse-content %}} -{{% collapse-content title="Handle initialization failures (optional)" level="p" %}} +### Handle initialization failures (optional) Consider handling initialization failures if your application can function with default flag values: {{< code-block lang="java" >}} @@ -568,15 +494,13 @@ try { {{< /code-block >}} If feature flags are critical for your application to function, let the exception propagate to prevent startup. -{{% /collapse-content %}} -{{% collapse-content title="Use consistent targeting keys" level="p" %}} +### Use consistent targeting keys Use consistent, stable identifiers as targeting keys: - **Good**: User IDs, session IDs, device IDs - **Avoid**: Timestamps, random values, frequently changing IDs -{{% /collapse-content %}} -{{% collapse-content title="Monitor flag evaluation" level="p" %}} +### Monitor flag evaluation Use the detailed evaluation results for logging and debugging: {{< code-block lang="java" >}} @@ -590,12 +514,9 @@ logger.info("Flag: {} | Value: {} | Variant: {} | Reason: {}", details.getReason() ); {{< /code-block >}} -{{% /collapse-content %}} ## Troubleshooting -{{% collapse-content title="Troubleshooting" level="p" %}} - ### Provider not ready **Problem**: `PROVIDER_NOT_READY` errors when evaluating flags @@ -695,8 +616,6 @@ Add `-Ddd.experimental.flagging.provider.enabled=true` to your Java command or s 3. Verify `DD_API_KEY` is correct 4. Check Agent logs for exposure upload errors -{{% /collapse-content %}} - ## Further reading {{< partial name="whats-next/whats-next.html" >}}