No results for ""
EXPAND ALL
  • Home
  • API docs

Java SDK 4.x to 5.0 migration guide

Read time: 15 minutes
Last edited: May 01, 2024

Overview

This topic explains how to adapt code that currently uses a 4.x version of the Java server-side SDK to use version 5.0 or later.

Before you migrate to 5.0, update to the latest 4.x version. Many of the changes that are mandatory in 5.0 were originally added in a 4.x version and made optional to use.

If you update to the latest 4.x version, deprecation warnings appear in areas of your code that need to be changed for 5.0. You can then update them while still using 4.x, rather than migrating everything simultaneously.

To learn more about updating to the latest 4.x version, visit the SDK's GitHub repository.

Identifying Java versions for the 5.0 SDK

The minimum version for LaunchDarkly SDK 5.0 is Java 8. LaunchDarkly no longer supports Java 7, per our End of Life policy. Because Java 8 is still in long-term support, you must use that version or later to use the version 5.0 SDK.

The primary impact of this in the public API is that the SDK now uses java.time.Duration to represent time intervals. Previously, these were represented by an integer number of milliseconds or seconds, depending on the case. Using Duration avoids that ambiguity. This only affects configuration methods.

Many other improvements have been made internally to take advantage of Java 8 features, but those do not require any changes in your code.

Using updated package names

The package naming convention has changed. Instead of com.launchdarkly.client, the main packages are now com.launchdarkly.sdk and com.launchdarkly.sdk.server. Because most of the class names have not changed, you may be able to fix your import statements with a find and replace.

The use of the word "client" in package names was potentially confusing, because even though this library is a service client that makes requests to the LaunchDarkly service, we built it to use in a server-side context. Classes that are specific to this server-side implementation are now in .sdk.server.

The base .sdk package contains classes that are not specific to server-side Java. They also exist in the Android SDK. When you view the SDK source code, you will find that the base .sdk package is implemented in a new repository, java-sdk-common.

To learn more about the differences between SDKs, read Client-side, server-side, and edge SDKs.

The full package schema is as follows:

  • com.launchdarkly.sdk
  • com.launchdarkly.sdk.json
  • com.launchdarkly.sdk.server
  • com.launchdarkly.sdk.server.interfaces
    • Provides: types like DataStore, formerly FeatureStore, and Event that are only used if you are writing a custom component. These were formerly in com.launchdarkly.client, so moving them makes the API much less cluttered.
  • com.launchdarkly.sdk.server.integrations
    • Provides: the same types that used to be in com.launchdarkly.client.integrations, added in 4.12.0.

Understanding changes to SDK configuration

Java SDK 4.12.0 and 4.13.0 added newer ways to use LDConfig. As of 5.0, those ways are mandatory. They are the only way to use LDConfig.

Instead of having many unrelated properties, each with their own LDConfig.Builder method, most of the properties are now grouped into areas of functionality, each of which has its own builder class that is only available if you are using that functionality. For instance, if you use streaming mode, there are options you can set to control streaming mode, and if you use polling mode, you use a different builder that does not have the streaming options. Similarly, if you have disabled analytics events, the event-related options are not accessible.

The basic areas of functionality are "data source" (polling, streaming, or file data), "data store" (memory or database), events, and networking.

If your code was already using the newer model, you should not need to change it except for changing the package names in your imports.

Understanding changes to data source methods

For each data source type, there is a factory method whose name ends in dataSource. These methods give you a builder object with methods for whatever options are appropriate. Pass that object to LDConfig.Builder.dataSource().

Here is an example:

// 4.x model: setting custom options for streaming mode
LDConfig config = new LDConfig.Builder()
.stream(true)
.reconnectTimeMs(2000)
.build();
// 4.x model: specifying polling mode and setting custom polling options
LDConfig config = new LDConfig.Builder()
.stream(false)
.pollingIntervalMillis(60000)
.build();

The default is to use streaming mode. Unlike the earlier model, it is no longer possible to construct a meaningless configuration such as "use streaming mode, but set the polling interval to 1 minute" or "disable events, but set the flush interval to 10 seconds."

Another data source option is the file-based data source. To learn more, read the API documentation for com.launchdarkly.sdk.server.integrations.FileData.

Understanding changes to the data store

As before, the default data store is a simple in-memory cache.

If you want to use a persistent datastore database integration, you must call a dataStore factory method for that integration. This gives you a builder object with whatever options are appropriate for that database. Next, pass the object to Components.persistentDataStore(), a wrapper that provides caching options that are not specific to any one database but are built into the SDK. Put this into LDConfig.Builder.dataStore().

To learn more, read Persistent data stores.

The following examples use the Redis integration:

// 4.x model: use Redis, set custom Redis URI and key prefix, set cache TTL to 45 seconds
LDConfig config = new LDConfig.Builder()
.featureStore(
Components.redisFeatureStore(URI.create("redis://my-redis-host"))
.prefix("my-prefix")
.cacheTime(45, TimeUnit.SECOND)
)
.build();

Understanding changes to events

Analytics events are enabled by default. To customize their behavior, call Components.sendEvents() to get a builder object with event-related options, and then pass that object to LDConfig.Builder.events().

To completely disable events, set LDConfig.Builder.events() to Components.noEvents().

If you use private user attributes and are configuring private attributes for the entire SDK rather than for individual users, there is a new syntax based on the new UserAttribute class. This ensures that built-in attributes are not misspelled and helps to distinguish these parameters from other kinds of string parameters.

Here is an example:

// 4.x model: disabling events
LDConfig config = new LDConfig.Builder()
.sendEvents(false)
.build();
// 4.x model: customizing event behavior
LDConfig config = new LDConfig.Builder()
.capacity(20000)
.flushInterval(10)
.privateAttributes("email", "name", "myCustomAttribute")
.build();

It is no longer possible to construct a meaningless configuration like "disable events, but set the flush interval to 10 seconds."

Understanding changes to networking

Options in this category affect how the SDK communicates with LaunchDarkly over HTTP/HTTPS, including connection timeout, proxy servers, and more. If you need to customize these, call Components.httpConfiguration() to get a builder object, configure this builder, and then pass it to LDConfig.Builder.http().

Some of the methods have changed slightly in terms of the type or number of parameters. More details are available in HttpConfigurationBuilder. The biggest difference is in how you specify an authenticated proxy server, which now uses a more general concept of authentication schemes.

Here is an example:

// 4.x model: setting connection and socket timeouts
LDConfig config = new LDConfig.Builder()
.connectTimeout(3)
.socketTimeout(4)
.build();
// 4.x model: specifying an HTTP proxy with basic authentication
LDConfig config = new LDConfig.Builder()
.proxyHost("my-proxy")
.proxyPort(8080)
.proxyUsername("user")
.proxyPassword("pass")
.build();

Understanding changes to the JSON value type

Before Java SDK 4.9.0, if you wanted to describe a value that could be of any valid JSON type (boolean, number, string, array, object, or null), you would use the Gson type JsonElement.

For example, LDClient.jsonVariation() returned that type, and also took a default value parameter of that type. However, there were two problems with using JsonElement:

  1. it made the SDK's public API dependent on third-party types, and
  2. the array and object types were mutable, which was a potential concurrency problem.

Version 4.9.0 added the immutable class LDValue as a replacement for this, along with LDClient.jsonValueVariation(), which is equivalent to jsonVariation, but uses LDValue, and overloads in LDUser.Builder.custom() for setting user attributes of any JSON type. As of version 5.0, this is now the only option and JsonElement is no longer exposed in the public API.

Here is an example of setting a user custom attribute to a list of strings:

// 4.x way: set user's "groups" to ["cats", "dogs"]
JsonArray groups = new JsonArray();
groups.add("cats");
groups.add("dogs");
LDUser user = new LDUser.Builder("key")
.custom("group", groups)
.build();
// The following shortcut method was exactly equivalent:
LDUser user = new LDUser.Builder("key")
.customString("group", Arrays.asList("cats", "dogs"))
.build();

Understanding changes to EvaluationDetail

The EvaluationDetail class, which was formerly in com.launchdarkly.client, and is now in com.launchdarkly.sdk, has been modified in the following two ways:

  1. The constructor is now private. To obtain an instance, call the factory method EvaluationDetail.fromValue() or EvaluationDetail.error(). This lets the SDK reuse instances for commonly used values, instead of allocating new ones.
  2. The variationIndex property is now an int rather than a nullable Integer. Instead of a null, the constant NO_VARIATION (-1) is used in the case where evaluation failed and so no flag variation was selected. This is similar to how Java's List.indexOf() and String.indexOf() can return -1 to mean "not found." The method isDefaultValue() still works as before.

Here is an example:

// 4.x model: create an EvaluationDetail instance, maybe for testing
EvaluationDetail<String> myValue = new EvaluationDetail<>(EvaluationReason.off(), 1, "x");
// 4.x model: check the variation index of the result
EvaluationDetail<String> resultDetail =
client.stringVariationDetail(flagKey, user, "default string value");
Integer variation = resultDetail.getVariationIndex();
if (variation == null) {
// do something for the default value case
// note that "if (resultDetail.isDefaultValue())" would also have worked
} else {
doSomethingWithVariation(variation.intValue());
}

Understanding changes to add-on integration packages

The Redis integration is no longer part of the main SDK distribution. It is now in a separate package: launchdarkly-java-server-sdk-redis-store on Maven, launchdarkly/java-server-sdk-redis on GitHub.

The Consul and DynamoDB integrations were already in separate packages, but they have new major versions that are compatible with Java SDK 5.0.

For the configuration syntax, read Data store above.

Understanding changes to logging

The SDK still uses SLF4J as a logging framework, but the use of logger names has changed. In SLF4J, logger names can be used to configure different filtering rules for different kinds of messages, so if you have been using that feature you may need to adjust your configuration.

Previously, most of the SDK's log output used the logger name com.launchdarkly.client.LDClient, but some of it used the names of other classes in the same package, such as com.launchdarkly.client.DefaultEventProcessor or com.launchdarkly.eventsource. These class names were implementation details that were subject to change, so there was not a well-defined set of logger names.

Starting in version 5.0, the SDK uses the following logger names:

  • com.launchdarkly.sdk.server.LDClient: general messages that do not fall into any other categories
  • com.launchdarkly.sdk.server.LDClient.DataSource: messages related to how the SDK obtains feature flag data-- normally this means messages about the streaming connection to LaunchDarkly, but if you use polling mode or file data instead, those will be logged under this name too
  • com.launchdarkly.sdk.server.LDClient.DataStore: messages related to how feature flag data is stored-- for instance, database errors if you are using a database integration
  • com.launchdarkly.sdk.server.LDClient.Evaluation: messages related to feature flag evaluation
  • com.launchdarkly.sdk.server.LDClient.Events: messages related to analytics event processing

The use of log levels has also changed somewhat. Previously, many kinds of I/O errors-- for instance, a network failure when the SDK was trying to make a streaming connection to LaunchDarkly, or trying to send analytics events-- were logged at ERROR level. This could cause an unwanted amount of noise from monitoring systems, because such errors were often due to temporary problems that the SDK could recover from without intervention.

Starting in version 5.0, if the SDK gets a network error that might interfere with receiving feature flag data from LaunchDarkly, it will log it at first at WARN level. Then it will continue retrying the connection as usual. If it remains unable to get a successful connection for a full minute, then it will report the problem again at the more serious ERROR level. You can change the connection interval using LoggingConfigurationBuilder.

Using the Relay Proxy

There are two ways you can configure the SDK to use the Relay Proxy:

  • proxy mode, connecting to it via HTTP/HTTPS just as if it were the LaunchDarkly service
  • daemon mode, receiving flag data only through a database.

A new syntax for configuring these was added in version 4.12.0, and in version 5.0 the new syntax is the only way.

Here is an example:

// 4.x model: proxy mode with streaming
URI relayUri = URI.create("http://my-relay-host:8000");
LDConfig config = new LDConfig.Builder()
.baseUri(relayUri)
.streamUri(relayUri)
.eventsUri(relayUri) // if you want to proxy events
.build();
// 4.x model: proxy mode with polling
URI relayUri = URI.create("http://my-relay-host:8000");
LDConfig config = new LDConfig.Builder()
.stream(false)
.baseUri(relayUri)
.eventsUri(relayUri) // if you want to proxy events
.build();
// 4.x model: daemon mode with a Redis database
LDConfig config = new LDConfig.Builder()
.featureStore(
Components.redisFeatureStore(URI.create("redis://my-redis-host"))
)
.useLdd(true)
.build();

Converting objects to or from JSON data

You might want to convert LaunchDarkly SDK classes such as LDUser, LDValue, or FeatureFlagsState into JSON if you are passing them to front-end JavaScript code. Less commonly, you might want to convert them from JSON. The SDK provides ways to do both.

First, you can use the serialize and deserialize methods in com.launchdarkly.sdk.json.JsonSerialization to quickly convert things to or from JSON strings.

Second, if you prefer to use the popular Gson or Jackson JSON frameworks for Java, read the following section.

Gson

Previously, classes from Gson were exposed in the SDK's public API, so gson had to be a regular dependency on your application classpath. This has been changed so that even though Gson is used internally by the SDK, it is a private shaded copy that does not interact with outside code. This means it cannot conflict with any other Gson dependency in your application.

However, this also means that if you want to convert LaunchDarkly SDK objects such as LDUser, LDValue, or FeatureFlagsState to or from JSON—for instance, to pass them to front-end JavaScript code—and you are using Gson, you can't simply call Gson.toJson() or Gson.fromJson() and get correct results. The Gson in your application does not automatically recognize the Gson annotations and custom serializers that are contained in the SDK.

To make this work correctly, you must add an extra step to the setup of your Gson instance:

import com.google.gson.*;
import com.launchdarkly.sdk.json.LDGson;
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(LDGson.typeAdapters())
// any other GsonBuilder options go here
.create();

If you have registered LDGson.typeAdapters() in the Gson instance you're using, toJson() and fromJson() will work as expected with LaunchDarkly types.

Jackson

Previously, it was not possible to convert LaunchDarkly SDK classes to or from JSON using Jackson. This is now possible as long as you configure your Jackson ObjectMapper with LDJackson.module() as follows:

import com.google.jackson.databind.*;
import com.launchdarkly.sdk.json.LDJackson;
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(LDJackson.module());

Using other third-party dependencies

Because Java 7 compatibility is no longer an issue, the SDK now uses more modern versions of the okhttp, guava, and gson packages internally.

As before, if you want to force the SDK to use the same versions of these dependencies that you are using, you can use the "thin" jar which does not embed them.

Implementing custom components

Most applications use either the default in-memory storage or one of the database integrations provided by LaunchDarkly, including Consul, DynamoDB, and Redis. However, the data store interface, formerly called "feature store," has always been public so developers can write their own integrations.

Starting in Java SDK 5.0, this model has been changed to make it easier to implement database integrations. The basic concepts are the same: the SDK defines its own "data kinds," such as feature flags and user segments. The data store must provide a way to add and update items of any of these kinds, without knowing anything about their properties except the key and version.

The main changes are:

  • Caching is now handled by the SDK, not by the store implementation. Now you only need to implement the lower-level operations of adding or updating data.
  • The SDK takes care of serializing and deserializing the data, so the store operates only on strings.
  • Data structures have been simplified to avoid the use of generics and maps.

The interface for "data source" components that receive feature flag data, either from LaunchDarkly or from some other source, such as a file, has also changed slightly to use the new data store model.

You may also want to review the source code for one of the LaunchDarkly database integrations, such as Redis, to learn how it changed from the previous major version.

Miscellaneous API changes

Here are several changes to names and types that do not affect the basic functionality of the SDK, but were changed for consistency.

  • The initialized() method of LDClient was renamed to isInitialized(), for consistency with Java naming conventions for methods that return a boolean.
  • The intVariation() and doubleVariation() methods of LDClient previously used the nullable types Integer and Double. They have been changed to use int and double, because the point of these type-specific methods is to be able to conveniently treat the feature flag value as a numeric type without having to worry about whether it is a null value or some other type. The boolVariation method already used the non-nullable boolean type, so it has not changed.

Understanding what was deprecated

All types and methods that were marked as deprecated in the last 4.x release have been removed from the 5.0 release. If you were using these with a recent version previously, you should already have received deprecation warnings at compile time, with suggestions about their recommended replacements.

For a full list of all deprecated symbols, read the release notes for 5.0.0.

The built-in New Relic integration has also been removed. This was never part of the public API, so it does not affect any application code.

Understanding new functionality

The 5.0 release also includes new features as described in the release notes.

These include:

To learn more, read the complete API documentation for these classes and methods.