Skip to main content

Java

Install the latest version

Github | Maven Repository

<dependency>
<groupId>cloud.prefab</groupId>
<artifactId>client</artifactId>
<version>0.3.20</version>
</dependency>

Dependency-Reduced Version

There's an optional uber-jar with shaded and relocated guava and failsafe dependencies

<dependency>
<groupId>cloud.prefab</groupId>
<artifactId>client</artifactId>
<version>0.3.20</version>
<classifier>uberjar</classifier>
</dependency>

Initialize the client

Basic Usage

final PrefabCloudClient prefabCloudClient = new PrefabCloudClient(new Options());

Typical Usage

We recommend using the PrefabCloudClient as a singleton in your application. This is the most common way to use the SDK.

// Micronaut Factory
@Factory
public class PrefabFactory {

@Singleton
public PrefabCloudClient prefabCloudClient() {
PrefabCloudClient.Options builder = new PrefabCloudClient.Options();
return new PrefabCloudClient(builder);
}

@Singleton
public FeatureFlagClient featureFlagClient(PrefabCloudClient prefabCloudClient) {
return prefabCloudClient.featureFlagClient();
}

@Singleton
public ConfigClient configClient(PrefabCloudClient prefabCloudClient) {
return prefabCloudClient.configClient();
}
}

Feature Flags

For boolean flags, you can use the featureIsOn convenience method:

public class MyClass {
// assumes you have setup a singleton
@Inject
private FeatureFlagClient featureFlagClient;

public String test(String key){
boolean val = featureFlagClient.featureIsOn(key);
return "Feature flag value of %s is %s".formatted(key, val);
}
}

Feature flags don't have to return just true or false. You can get other data types using get:

public class MyClass {
// assumes you have setup a singleton
@Inject
private FeatureFlagClient featureFlagClient;

public String test(String key){
Optional<Prefab.ConfigValue> val = featureFlagClient.get(key);
return "Feature flag value of %s is %s".formatted(key, val.orElse("no value found"));
}
}

Context

To finely-target configuration rule evaluation, we accept contextual information globally, request-scoped (thread-locally) with the ContextStore which will affect all logging, featureflag and config lookups.

Global Context

Use global context for information that doesn't change - for example, your application's key, availability-zone etc. Set it in the client's options as below


PrefabContext deploymentContext = PrefabContext
.newBuilder("application")
.put("key", "my-api")
.put("az", "1a")
.put("type", "web")
.build();

Options options = new Options()
.setGlobalContext(PrefabContextSet.from(deploymentContext))

Thread-local (Request-scoped)

// set the thread-local context
prefabCloudClient.configClient().getContextStore().addContext(
PrefabContext.newBuilder("User")
.put("name", user.getName())
.put("key", user.getKey())
.build());

// or using an autoclosable scope helper
// this will replace any-existing threadlocal context until the try-with-resources block exits
PrefabContextHelper prefabContextHelper = new PrefabContextHelper(
prefabClient.configClient()
);

try (
PrefabContextHelper.PrefabContextScope ignored = prefabContextHelper.performWorkWithAutoClosingContext(
PrefabContext.newBuilder("User")
.put("name", user.getName())
.put("key", user.getKey())
.build());
)
) {
// do config/flag operations

}





When thread-local context is set, log levels and feature flags will evaluate in that context. Here are details on setting thread-local context:

Add a filter to add a prefab context based on the currently "logged in" user.

@Filter(Filter.MATCH_ALL_PATTERN)
public class PrefabContextFilter implements HttpFilter {

private final ConfigClient configClient;

@Inject
PrefabContextFilter(ConfigClient configClient) {
this.configClient = configClient;
}

@Override
public Publisher<? extends HttpResponse<?>> doFilter(HttpRequest<?> request, FilterChain chain) {

request.getUserPrincipal(Authentication.class).ifPresent(authentication ->
{
User user = (User) authentication.getAttributes().get(ExampleAuthenticationProvider.USER_ATTR);
configClient.getContextStore()
.addContext(
PrefabContext.newBuilder("user")
.put("id", user.id())
.put("country", user.country())
.put("email", user.email())
.build()
);
}
);
return chain.proceed(request);
}

@Override
public int getOrder() {
return ServerFilterPhase.SECURITY.after() + 1;
// run after the DefaultLoginFilter
}
}

Prefab Context uses ThreadLocals by default. In event-based frameworks like micronaut, that won't work so configure the Prefab Context store to use ServerRequestContextStore instead.

options.setContextStore(new ServerRequestContextStore());

Learn more with the Prefab + Micronaut example app

Just-in-time Context

You can also provide context information inline when making a get request. If you provide just-in-time context to your FF or config evaluations, it will be merged with the global context.

featureFlagClient.featureIsOn(
"features.example-flag",
PrefabContext.newBuilder("customer")
.put("group", "beta")
.build()
)

prefabCloudClient.configClient().get("the.key",
PrefabContext.newBuilder("user")
.put("name", "james")
.put("tier", "gold")
.put("customerMonths", 12)
.build()
)

See contexts for more information

Dynamic Config

final Optional<Prefab.ConfigValue> configValue = prefabCloudClient.configClient().get("the.key");
if(configValue.isPresent()){
System.out.println(configValue.get().getString());
}

Live Values

Live values are a convenient and clear way to use configuration throughout your system. Inject a prefab client and get live values for the configuration keys you need.

In code, .get() will return the value. These values will update automatically when the configuration is updated in Prefab Cloud.

import java.util.function.Supplier;

class MyClass {

private Supplier<String> sampleString;
private Supplier<Long> sampleLong;

@Inject
public MyClass(ConfigClient configClient) {
this.sampleString = configClient.liveString("sample.string");
this.sampleLong = configClient.liveLong("sample.long");
}

public String test(){
return "I got %s and %d from Prefab Cloud.".formatted(sampleString.get(), sampleLong.get());
}
}

Dynamic Logging with the Java SDK

Setting up a dynamic logger with Prefab is easy.

We need to give our ConfigClient a LoggingListener when we create it. This listener will be called whenever the config changes, it will detect LogLevel changes, find the appropriate logger based on the config key, and map the LogLevel to the appropriate Logger specific level.

Prefab comes with support for most common logging platforms

Java Util Logging is supported within the client.

Other Logging Platforms are provided in separate maven dependencies:

Set Up Dynamic Logging With LogBack

To set up dynamic logging with LogBack, we need to install the PrefabMDCTurboFilter as seen in the configClient() method below.

You may want to make sure your bean initializes on @Context so it is available immediately.

// this is a Micronaut example
@Factory
public class PrefabFactory {

@Singleton
public PrefabCloudClient prefabCloudClient() {
final Options options = new Options();
return new PrefabCloudClient(options);
}

@Singleton
public FeatureFlagClient featureFlagClient(PrefabCloudClient prefabCloudClient) {
return prefabCloudClient.featureFlagClient();
}

// in Micronaut @Context is equivalent to eager-singleton
@Context
public ConfigClient configClient(PrefabCloudClient prefabCloudClient) {
ConfigClient client = prefabCloudClient.getClient();
PrefabMDCTurboFilter.install(client);
return client;
}
}

Now we can set our log levels dynamically in the UI and they will update immediately.

example screenshot

Targeted Log Levels

You can use Targeting to change your log levels based on the current user/request/device context using our rules engine.

Telemetry

By default, Prefab uploads telemetry that enables a number of useful features. You can alter or disable this behavior using the following options:

NameDescriptionDefault
collectEvaluationSummariesSend counts of config/flag evaluation results back to Prefab to view in web apptrue
collectLoggerCountsSend counts of logger usage back to Prefab to power log-levels configuration screentrue
contextUploadModeUpload either context "shapes" (the names and data types your app uses in prefab contexts) or periodically send full example contextsPERIODIC_EXAMPLE

If you want to change any of these options, you can pass an options object when initializing the Prefab client.

Options options = new Options()
.setCollectEvaluationSummaries(true)
.setCollectLoggerCounts(true)
.setContextUploadMode(Options.CollectContextMode.PERIODIC_EXAMPLE);

Testing

Prefab suggests testing with generous usage of Mockito. We also provide a useful FixedValue for testing Live Values.

@Test
void testPrefab(){
ConfigClient mockConfigClient = mock(ConfigClient.class);
when(mockConfigClient.liveString("sample.string")).thenReturn(FixedValue.of("test value"));
when(mockConfigClient.liveLong("sample.long")).thenReturn(FixedValue.of(123L));

MyClass myClass = new MyClass(mock(ConfigClient.class));

// test business logic

}

Reference

Options

Options options = new Options()
.setConfigOverrideDir(System.getProperty("user.home"))
.setApikey(System.getenv("PREFAB_API_KEY"))
.setPrefabDatasource(Options.Datasources.ALL) // Option: Datasources.LOCAL_ONLY
.setOnInitializationFailure(Options.OnInitializationFailure.RAISE) // Option Options.OnInitializationFailure.UNLOCK
.setInitializationTimeoutSec(10)
.setGlobalContext(PrefabContextSet.from(PrefabContext
.newBuilder("application")
.put("key", "my-api")
.put("az", "1a")
.put("type", "web")
.build())
);

Option Definitions

NameDescriptionDefault
collectEvaluationSummariesSend counts of config/flag evaluation results back to Prefab to view in web apptrue
collectLoggerCountsSend counts of logger usage back to Prefab to power log-levels configuration screentrue
contextUploadModeUpload either context "shapes" (the names and data types your app uses in prefab contexts) or periodically send full example contextsPERIODIC_EXAMPLE
onInitializationFailureChoose to crash or continue with local data only if unable to fetch config data from prefab at startupRAISE (crash)
prefabDatasourcesUse either only-local data or local + API dataALL
globalContextset a static context to be used as the base layer in all configuration evaluationEMPTY