---
title: "OpenFeature Java Provider"
description: "ConfigDirector Provider for the OpenFeature Java SDK"
canonical_url: "https://docs.configdirector.com/sdks/openfeature/java"
---
# OpenFeature Java Provider

> ConfigDirector Provider for the OpenFeature Java SDK

<div hidden="true" dataTitleIcon="openfeature-java">



</div>

<external-badges package-url="/sdks/maven-repository" registry="maven" repo="java-sdks/tree/main/configdirector-openfeature-server-provider">



</external-badges>

## Introduction

The OpenFeature Java Provider is intended to be used in combination with the [OpenFeature Java SDK](https://openfeature.dev/docs/reference/sdks/server/java/). The provider wraps the ConfigDirector [Java SDK](https://docs.configdirector.com/sdks/server/java).

The minimum Java version supported is `17`.

The provider is thread safe. Register one instance when your application starts, and shut OpenFeature down when it stops. Evaluations read config state the provider already holds in memory, so they make no network calls on the request path.

## Installation

The provider is published to the [ConfigDirector Maven repository](https://docs.configdirector.com/sdks/maven-repository). Add the repository and the dependency to your build. If your Gradle build declares its repositories in `settings.gradle.kts` or `settings.gradle`, under `dependencyResolutionManagement`, the repository block goes there instead.

<code-group>

```kotlin [build.gradle.kts]
repositories {
    mavenCentral()
    exclusiveContent {
        forRepository {
            maven {
                name = "ConfigDirector"
                url = uri("https://maven.configdirector.com")
            }
        }
        filter {
            includeGroup("com.configdirector")
        }
    }
}

dependencies {
    implementation("com.configdirector:openfeature-server-provider:1.8.0")
}
```

```groovy [build.gradle]
repositories {
    mavenCentral()
    exclusiveContent {
        forRepository {
            maven {
                name = 'ConfigDirector'
                url = 'https://maven.configdirector.com'
            }
        }
        filter {
            includeGroup 'com.configdirector'
        }
    }
}

dependencies {
    implementation 'com.configdirector:openfeature-server-provider:1.8.0'
}
```

```xml [pom.xml]
<repositories>
  <repository>
    <id>configdirector</id>
    <url>https://maven.configdirector.com</url>
    <releases><enabled>true</enabled></releases>
    <snapshots><enabled>false</enabled></snapshots>
  </repository>
</repositories>

<dependency>
  <groupId>com.configdirector</groupId>
  <artifactId>openfeature-server-provider</artifactId>
  <version>1.8.0</version>
</dependency>
```

</code-group>

The OpenFeature Java SDK and the ConfigDirector Java SDK are included as transitive dependencies.

## Configure and initialize the client

1. Create an instance of the provider using your server SDK key. You can retrieve a server SDK key for each environment under `SDK keys` in the dashboard's navigation panel.
2. Set the OpenFeature provider.
3. Get a client instance from OpenFeature.

```java [ConfigDirectorSetup.java]
import com.configdirector.openfeature.ConfigDirectorProvider;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;

OpenFeatureAPI api = OpenFeatureAPI.getInstance();
try {
    api.setProviderAndWait(new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY"));
} catch (ProviderNotReadyError error) {
    System.err.println("Starting on in-code default values until ConfigDirector sends config: " + error.getMessage());
}
Client client = api.getClient();
```

<warning>

Server SDK keys are secret values. Do not commit them to your source code repository. Provide them at runtime via environment variables instead.

</warning>

### When no config arrives in time

`setProviderAndWait` throws `ProviderNotReadyError` when no config arrives from ConfigDirector within the connection timeout, which is 3 seconds unless you set the connection's [`timeout`](https://docs.configdirector.com/sdks/server/java#additional-configuration-options), or when ConfigDirector rejects the server SDK key. The provider then reports the `ERROR` state, with a `PROVIDER_ERROR` event, and after a timeout it keeps connecting in the background. Until config arrives, flags resolve to their in-code default values with the `PROVIDER_NOT_READY` error code. Once it arrives, the provider reports `READY`, with one `PROVIDER_READY` event.

The example above catches the exception, logs it and starts on in-code default values. To start without waiting for ConfigDirector at all, set the provider with `setProvider` instead:

```java [ConfigDirectorSetup.java]
OpenFeatureAPI.getInstance().setProvider(new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY"));
```

Before version 1.8.0 of the provider, `setProviderAndWait` returned after the timeout, and the provider reported `READY` while it served in-code default values.

### Additional configuration options

Additional configuration options can be passed into the provider in the optional second argument of the constructor.

For example, the `metadata` can be provided like this:

```java [ConfigDirectorSetup.java]
import com.configdirector.openfeature.ConfigDirectorProvider;

ConfigDirectorProvider provider = new ConfigDirectorProvider(
    "YOUR-SERVER-SDK-KEY",
    options -> options.metadata("YOUR-APP-NAME", "1.0.2"));
```

The provider accepts the same configuration options as the Java SDK client, [refer to the additional configuration options section of the Java SDK for a full list](https://docs.configdirector.com/sdks/server/java#additional-configuration-options).

That includes [running without a connection](https://docs.configdirector.com/sdks/server/java#run-without-a-connection): built with `new ConfigDirectorProvider(options -> ...)` and no server SDK key, the provider serves the values you give it without connecting to ConfigDirector, and `setProviderAndWait` throws if those values cannot be loaded.

### Shut down

Shutting OpenFeature down closes the provider, which closes its connections and reports any pending telemetry:

```java [Main.java]
OpenFeatureAPI.getInstance().shutdown();
```

## Retrieve config values

To retrieve config values, use the OpenFeature client:

```java [Main.java]
boolean booleanValue = client.getBooleanValue("my-config-key", false);

String stringValue = client.getStringValue("my-string-config-key", "Default");
```

Each OpenFeature getter maps to a ConfigDirector config type:

<table>
<thead>
  <tr>
    <th>
      OpenFeature getter
    </th>
    
    <th>
      ConfigDirector config value
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        getBooleanValue
      </code>
    </td>
    
    <td>
      Boolean
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        getStringValue
      </code>
    </td>
    
    <td>
      String or enum
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        getIntegerValue
      </code>
      
      , <code>
        getLongValue
      </code>
      
      , <code>
        getDoubleValue
      </code>
    </td>
    
    <td>
      Number
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        getObjectValue
      </code>
    </td>
    
    <td>
      JSON object or JSON array
    </td>
  </tr>
</tbody>
</table>

For `getObjectValue`, the default value decides the expected shape: provide a structure for a JSON object, or a list for a JSON array.

```java [Main.java]
import dev.openfeature.sdk.ImmutableStructure;
import dev.openfeature.sdk.Value;

Value settings = client.getObjectValue("my-json-config-key", new Value(new ImmutableStructure()));
String theme = settings.asStructure().getValue("theme").asString();
```

For additional information regarding the OpenFeature client [refer to the OpenFeature Java SDK documentation](https://openfeature.dev/docs/reference/sdks/server/java/).

### Evaluation details

The detailed getters of the OpenFeature client, such as `getBooleanDetails`, report why an evaluation produced the value that it did:

<table>
<thead>
  <tr>
    <th>
      Outcome
    </th>
    
    <th>
      Reason
    </th>
    
    <th>
      Error code
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A value was found
    </td>
    
    <td>
      <code>
        TARGETING_MATCH
      </code>
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      The config carries no value
    </td>
    
    <td>
      <code>
        DEFAULT
      </code>
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      The config key is unknown
    </td>
    
    <td>
      <code>
        ERROR
      </code>
    </td>
    
    <td>
      <code>
        FLAG_NOT_FOUND
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      No config state has arrived yet
    </td>
    
    <td>
      <code>
        ERROR
      </code>
    </td>
    
    <td>
      <code>
        PROVIDER_NOT_READY
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The value does not match the requested type
    </td>
    
    <td>
      <code>
        ERROR
      </code>
    </td>
    
    <td>
      <code>
        TYPE_MISMATCH
      </code>
    </td>
  </tr>
</tbody>
</table>

When a value was found, the `variant` is ConfigDirector's identifier for that value. In every other case the in-code default value is returned.

## Context

The context can be provided as the third argument to value getter functions of the OpenFeature client. The OpenFeature Java provider evaluates targeting rules locally without additional network calls for different contexts.

```java [Main.java]
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.ImmutableStructure;
import dev.openfeature.sdk.Value;
import java.util.Map;

// Any arbitrary traits which can be referenced in targeting rules
ImmutableStructure traits = new ImmutableStructure(Map.of("region", new Value("North America")));

EvaluationContext context = new ImmutableContext(
    "12345", // In OpenFeature, the targeting key represents the context's identifier
    Map.of("name", new Value("Example User"), "traits", new Value(traits)));

boolean booleanValue = client.getBooleanValue("my-config-key", false, context);
```

To evaluate flags for an entity other than a user, such as a tenant, set the context kind with a top-level `kind` attribute:

```java [Main.java]
EvaluationContext tenant = new ImmutableContext(
    "acme",
    Map.of(
        "kind", new Value("tenant"),
        "name", new Value("Acme Inc."),
        "traits", new Value(new ImmutableStructure(Map.of("plan", new Value("enterprise"))))));

boolean booleanValue = client.getBooleanValue("my-config-key", false, tenant);
```

Leaving `kind` out means `user`. A `kind` inside `traits` is an ordinary trait, not the context kind. A context kind is 1 to 64 lowercase letters, digits, dashes, or underscores. A context with an invalid context kind is ignored: the SDK logs an error, and flags still resolve successfully, as if no context had been given. Context kinds are available from version 1.6.0 of the provider.

The evaluation context maps onto the ConfigDirector context as follows:

<table>
<thead>
  <tr>
    <th>
      OpenFeature evaluation context
    </th>
    
    <th>
      ConfigDirector context
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      The targeting key, or otherwise an <code>
        id
      </code>
      
       attribute
    </td>
    
    <td>
      <code>
        id
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The top-level <code>
        kind
      </code>
      
       attribute
    </td>
    
    <td>
      <code>
        kind
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The <code>
        name
      </code>
      
       attribute
    </td>
    
    <td>
      <code>
        name
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The <code>
        traits
      </code>
      
       structure attribute
    </td>
    
    <td>
      <code>
        traits
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The boolean <code>
        anonymous
      </code>
      
       attribute
    </td>
    
    <td>
      <code>
        anonymous
      </code>
    </td>
  </tr>
</tbody>
</table>

For additional information regarding the OpenFeature client [refer to the OpenFeature Java SDK documentation](https://openfeature.dev/docs/reference/sdks/server/java/).

## Events

The provider emits `PROVIDER_CONFIGURATION_CHANGED` whenever configs are updated on the dashboard or via the admin API, carrying the keys of the configs in the update:

```java [Main.java]
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(
    details -> System.out.println("Configs updated: " + details.getFlagsChanged()));
```

When no config arrived in time at startup, the provider emits `PROVIDER_READY` once config arrives, as described in [When no config arrives in time](#when-no-config-arrives-in-time).

## Test your code

Code that reads flags through OpenFeature is tested by swapping the provider, so it never has to change for a test and nothing from ConfigDirector is involved. The OpenFeature Java SDK ships an in-memory provider for that, `InMemoryProvider`, which serves the values your test sets.

Each flag is a `Flag` built from its named variants and the `defaultVariant` it resolves to:

```java [CheckoutTest.java]
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.providers.memory.Flag;
import dev.openfeature.sdk.providers.memory.InMemoryProvider;

class CheckoutTest {

    private final OpenFeatureAPI api = OpenFeatureAPI.getInstance();

    @AfterEach
    void shutOpenFeatureDown() {
        api.shutdown();
    }

    @Test
    void showsTheNewCheckout() {
        Map<String, Flag<?>> flags = new HashMap<>();
        flags.put("new-checkout", Flag.builder().variant("on", true).variant("off", false).defaultVariant("on").build());
        flags.put("max-items", Flag.builder().variant("default", 20).defaultVariant("default").build());
        InMemoryProvider provider = new InMemoryProvider(flags);
        api.setProviderAndWait(provider);
        Client client = api.getClient();

        assertTrue(client.getBooleanValue("new-checkout", false));
        assertEquals(20, client.getIntegerValue("max-items", 0));

        provider.updateFlags(Map.of(
            "new-checkout", Flag.builder().variant("on", true).variant("off", false).defaultVariant("off").build()));
        assertFalse(client.getBooleanValue("new-checkout", true));
    }
}
```

`updateFlags` adds or replaces the flags it is given, leaves the others as they are, and emits `PROVIDER_CONFIGURATION_CHANGED` with the keys it was given; `updateFlag` does the same for one flag. A flag can also carry a `contextEvaluator`, a function from the evaluation context to the value to serve, for a test of code that targets specific users. The OpenFeature API is one global, so shut it down after each test with `api.shutdown()`, as above, which lets the next test register its own provider.


## Sitemap

See the full [sitemap](https://docs.configdirector.com/sitemap.md) for all pages.
