> ## Documentation Index
> Fetch the complete documentation index at: https://launchdarkly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Android SDK 3.x to 4.0 migration guide

<View title="Developer" />

<View title="Federal docs" />

<View title="EU docs" />

This topic explains the changes in the Android 4.0 release and how to migrate to that version.

**Version 4.0 includes several breaking changes**. Additionally, if you use the Relay Proxy, you must update your Relay Proxy to version 7.0 before you update your SDK to version 4.0. To learn more, read the [Relay Proxy 7.0 release notes](https://github.com/launchdarkly/ld-relay/releases/tag/v7.0.0). To upgrade to the latest Relay Proxy version, visit [Relay Proxy releases](https://github.com/launchdarkly/ld-relay/releases) on GitHub.

Before you migrate to version 4.0, update to the latest 3.x version. Some of the changes that are mandatory in 4.0 were originally added in a 3.x version and made optional.

If you update to the latest 3.x version, deprecation warnings appear in areas of your code that need to be changed for 4.0. You can update them at your own pace while still using 3.x, rather than migrating everything simultaneously. To learn more about updating to the latest 3.x version, visit the [SDK's GitHub repository](https://github.com/launchdarkly/android-client-sdk).

## Understanding contexts

Many LaunchDarkly customers create targeting rules for feature flags based on a variety of different information, including attributes pertaining to users, organizations, devices, and more. In previous versions of the LaunchDarkly SDK, you could define this information in a user object, using a combination of built-in and custom attributes. Now you can define this information in a more structured way, using **contexts**.

Each context has a required attribute called `kind` that you can use to categorize context instances for targeting and Experimentation. You can also add other attributes. Attributes can be strings, booleans, numbers, arrays, or JSON objects.

When you evaluate a feature flag within your application, the flag's targeting rules use information from one or more kinds of contexts. For example, you may know:

* the username, first name, last name, and email address of a person, as part of a context with `kind` of "user"
* the company, department, and location of an organization, as part of a context with `kind` of "organization"
* the device, model, and operating system of an environment, as part of a context with `kind` of "device"

This new version of the LaunchDarkly SDK requires you to evaluate feature flags using an **evaluation context**, which is an object containing one or more contexts.

To learn more about contexts, read [Contexts](/docs/home/flags/contexts).

## Migrating from users to contexts

The 4.0 version of this SDK lets you use contexts. When you migrate from version 3.x, replace every instance of a user with a context. If there are any instances you do not replace, the 4.0 version of the Android SDK will convert each `LDUser` parameter it receives to `LDContext` and call the `LDContext`-specific version of the method.

<Note>
  **LaunchDarkly assumes older versions of the SDK use <code>user</code> contexts**

  A context always has a `kind` attribute. When older versions of the SDK send events to LaunchDarkly, LaunchDarkly will convert the users in those events to contexts with a `kind` of `user`.

  If a flag configuration specifies any context `kind`s other than `user`, older versions of the Android SDK will not evaluate the flag correctly. You must upgrade your SDK if you are going to use context `kind`s other than `user` in your flag configurations.
</Note>

The primary differences between working with users and working with contexts include the following:

* [Create contexts, not users](#understanding-differences-between-users-and-contexts): Where you previously created users, now you must create contexts.
* [Changes to attributes](#understanding-changes-to-built-in-and-custom-attributes): There are now fewer built-in attributes. You can still add as many custom attributes as you like, although the format has changed slightly. A flag's targeting rules can now address fields within a JSON object.
* [Changes to private attributes](#understanding-changes-to-private-attributes): You can mark specific attributes of a context as private, either across all contexts of any kind, or within a particular context or context kind.
* [Changes to automatic attribute population](#understanding-changes-to-automatic-custom-property-population): The SDK no longer automatically populates the "device" and "os" properties.
* [Changes to anonymous users](#understanding-changes-to-anonymous-users): The SDK  must be configured to automatically generate a context key.
* [Changes to alias events](#understanding-changes-to-alias-events): The alias method has been removed.

To learn more about upgrading to contexts, read [Best practices for upgrading users to contexts](/docs/guides/flags/upgrading-contexts).

### Understanding differences between users and contexts

Where you previously created users, now you must create contexts.

Here's how to construct a basic context, as compared with constructing a user:

<CodeGroup>
  ```java title="3.x syntax, user with key" lines wrap theme={null}
  LDUser user = new LDUser("example-user-key");
  ```

  ```java title="4.0 syntax, context with key" lines wrap theme={null}
  LDContext context = LDContext.create("example-context-key");
  ```
</CodeGroup>

Here's how to construct a basic context, with a context kind of something other than "user":

<CodeGroup>
  ```java title="4.0 syntax, context shortcut" lines wrap theme={null}
  LDContext context1 = LDContext.create(ContextKind.of("organization"), "example-organization-key");
  ```
</CodeGroup>

Here's how to construct a multi-context, which includes multiple context kinds:

<CodeGroup>
  ```java title="4.0 syntax" lines wrap theme={null}
  LDContext userContext = LDContext.create("example-user-key");
  LDContext deviceContext = LDContext.create(ContextKind.of("device"), "example-device-key");

  LDContext multiContext = LDContext.createMulti(
      userContext,
      deviceContext
  );
  ```
</CodeGroup>

### Understanding changes to built-in and custom attributes

This section describes the changes to built-in and custom attributes in the 4.0 version of the SDK.

#### Working with built-in and custom attributes

In previous SDK versions, the user object included several built-in attributes for describing the user. It also included optional custom attributes, which you could add to a `custom` object within the user object and then populate.

In version 4.0, the only built-in attributes are `kind`, `key`, `name`, and `anonymous`. `Kind`, `key`, and `name` are strings, and `anonymous` is a boolean.

You can define additional attributes for a context by passing in a name and value for each. Additional attributes can be any JSON type, including boolean, number, string, array, or object. In version 4.0, you do not need to add custom attributes within a `custom` object.

Here's how to construct a context with additional attributes, as compared with constructing a similar user:

<CodeGroup>
  ```java title="3.x syntax, user with attributes" lines wrap theme={null}
  LDUser user = new LDUser.Builder("example-user-key")
    .name("Sandy")
    .email("sandy@example.com")
    .custom("group", "Global Health Services")
    .build();
  ```

  ```java title="4.0 syntax, context with attributes" lines wrap theme={null}
  LDContext context = LDContext.builder("example-user-key")
    .name("Sandy")
    .set("email", "sandy@example.com")
    .set("group", "Global Health Services")
    .build();
  ```
</CodeGroup>

#### Referencing properties of an attribute object

In previous versions of the SDK, if you set the value of a user's custom attribute to an object, you could not reference that object in evaluations. In version 4.0, if a context attribute's value is a JSON object, you can reference properties of that object as the attribute in the targeting rules for a flag or segment.

Here's one way to add object attributes to a context:

<CodeGroup>
  ```java title="4.0 syntax, context with object attributes" lines wrap theme={null}
  LDValue addressData = LDValue.buildObject()
    .put("street", "Main St")
    .put("city", "Springfield")
    .build();
  LDContext context = LDContext.builder("example-user-key")
    .set("address", addressData)
    .build();
  ```
</CodeGroup>

There are multiple ways you can construct attribute values. To learn more about additional methods, read [`LDValue`](https://launchdarkly.github.io/java-server-sdk/com/launchdarkly/sdk/LDValue.html).

In your flag or segment targeting, use `/` as the delimiter to refer to specific object fields. For example, you can use `/address/city` in your targeting. To learn more, read [Target with flags](/docs/home/flags/target).

#### Removing the secondary attribute

In previous versions of the SDK, you could set the value of a user's `secondary` attribute, as an optional secondary key for a user. The SDK would incorporate this attribute into the variation bucket assignment hash.

In version 4.0, the `secondary` attribute has been removed. If you were previously using this attribute as part of distinguishing [percentage rollouts](/docs/home/releases/percentage-rollouts#percentage-rollout-logic), that will no longer work for your users.

### Understanding changes to private attributes

As in previous versions of the SDK, you can mark specific attributes of a context as private. This restricts the context data your application sends to LaunchDarkly, while still using that data for flag targeting.

In version 4.0, there are two scopes for which you can mark attributes as private:

* Across all contexts of any context kind. You might use this if you want to ensure that the SDK never stores an "email" attribute in LaunchDarkly, no matter whether it occurs in a user context, an organization context, or something else.
* Within a particular context or context kind. You might use this if you want an "email" attribute to be private in a user context, but not in an organization context.

In the first example, all attributes are marked private for all contexts. In the second example, the "email" and "address" attributes are private for all contexts:

<CodeGroup>
  ```java title="4.0 syntax, all attributes private" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
  .events(
    Components.sendEvents().allAttributesPrivate(true)
  )
  .build();
  ```

  ```java title="4.0 syntax, two attributes private" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
  .events(
    Components.sendEvents().privateAttributes("email", "address")
  )
  .build();
  ```
</CodeGroup>

To learn more, read [EventProcessorBuilder](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.html).

Here's how to mark an attribute as private for a particular context:

<CodeGroup>
  ```java title="4.0 syntax, attribute marked private for one context" lines wrap theme={null}
  LDContext context = LDContext.builder("key")
    .name("Sandy")
    .set("email", "sandy@example.com")
    .privateAttributes("email")
    .build();
  ```
</CodeGroup>

For attributes that are objects, you can mark specific fields private, using the `/` delimiter followed by the attribute name, then the `/` delimiter followed by the JSON property within the value.

For example, for the attribute `"address": { "street": "Main St", "city": "Springfield" }`, you could set just the `/address/street` as private.

The `privateAttributeNames` attribute that existed in a user object in version 3.x has been renamed to `privateAttributes` in `LDContext`. To learn more, read [`privateAttributes`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/ContextBuilder.html#privateAttributes\(java.lang.String...\)).

### Understanding changes to automatic custom property population

In the 3.x version, the SDK automatically populated the `device` and `os` built-in attributes of a user. In version 4.0, the SDK will not automatically create or populate these attributes for contexts.

If you currently have feature flags that target these attributes, you will need to determine what context you want them to be part of, and populate them yourself.

Here's how:

<CodeGroup>
  ```java title="4.0 syntax, setting device and os" lines wrap theme={null}
  LDContext context = LDContext.builder("example-context-key")
      .set("os", "25")
      .set("device", "Pixel XL marlin")
      .build();
  ```
</CodeGroup>

### Understanding changes to anonymous users

In previous versions of the SDK, setting a user's `key` attribute to `null` and its `anonymous` attribute to `true` caused the SDK to generate a unique identifier to use as a key.

In the 4.0 version of the SDK, this behavior works differently. The SDK will not generate keys for anonymous contexts unless you set a new configuration option.

Here's how:

<CodeGroup>
  ```java title="4.0 syntax, configuring the SDK to generate keys" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
      .generateAnonymousKeys(true)
      .build();
  ```
</CodeGroup>

If you set this option, the SDK will generate a key for any context whose `anonymous` attribute is `true`. You must still specify a non-null key as a placeholder when you construct the `LDContext`, because the SDK does not allow an `LDContext` to exist with a null key. When you pass this context to SDK methods like `init` or `identify`, the SDK replaces the placeholder key with a generated key.

In this example, the placeholder key is "unknown-context-key", but it could be any non-empty string:

<CodeGroup>
  ```java title="4.0 syntax, building an anonymous context" lines wrap theme={null}
  LDContext context = LDContext.builder("unknown-context-key")
      .anonymous(true)
      .build();
  ```
</CodeGroup>

If you use multi-contexts, this feature applies to each individual context separately. Only the contexts that have the `anonymous` attribute set to `true` will receive a generated key, and there will be a different generated key for each context kind.

In this example, `userContext` will receive a generated key, but `orgContext` will still have the key \`"example-organization-key":

<CodeGroup>
  ```java title="4.0 syntax, anonymous context in a multi-context" lines wrap theme={null}
  LDContext userContext = LDContext.builder("unknown-context-key")
      .anonymous(true)
      .build();
  LDContext orgContext = LDContext.create(ContextKind.of("organization"), "example-organization-key");
  LDContext multiContext = LDContext.createMulti(userContext, orgContext);
  ```
</CodeGroup>

### Understanding changes to alias events

In previous versions of the SDK, multiple user objects could represent one person. For example, this could happen the first time a person logged in to your application. The person might be represented by an anonymous user before they logged in, and a different user after they logged in. You could associate these two LaunchDarkly users by sending an `alias` event in the SDK.

With the introduction of contexts, the person in this scenario is represented by two different context kinds. For example, before they log in, they might be represented by a device context. After they log in, they might be represented by a multi-context, for example, by one context kind based on their device and simultaneously by another context kind based on their user information.

The 4.0 version of the SDK removes the ability to send an `alias` event. If you currently [alias users](/docs/sdk/features/aliasing-users), you will need to remove this code when you migrate to version 4.0.

If you want to continue associating two contexts with each other, you can use two different context kinds, and then identify a multi-context that includes both individual contexts when you want the association to occur. Unlike the aliasing method, the association doesn't persist between calls. You must send the contexts you want to associate in each `variation` or `identify` call and each `track` call.

Here's how:

<CodeGroup>
  ```java title="4.0 syntax, associating two contexts" lines wrap theme={null}
  LDContext context1 = LDContext.create("example-user-key");
  LDContext context2 = LDContext.create(ContextKind.of("device"), "example-device-key");
  LDContext multiContext = LDContext.createMulti(context1, context2);
  client.identify(multiContext);
  ```
</CodeGroup>

To learn more, read [`ContextMultiBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/ContextMultiBuilder.html).

## Understanding changes to SDK configuration

Android SDK [3.3.0](https://github.com/launchdarkly/android-client-sdk/releases/tag/3.3.0) added newer ways to use [`LDConfig.Builder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html). As of 4.0, those ways are mandatory. They are the only way to use `LDConfig.Builder`.

Instead of having many unrelated properties, each with their own [`LDConfig.Builder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html) 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," events, networking, and service URIs. Data source refers to polling, streaming, or test data.

If your code was already using the newer model, you should not need to change it.

### 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()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html#dataSource\(com.launchdarkly.sdk.android.subsystems.ComponentConfigurer\)).

Here is an example:

<CodeGroup>
  ```java title="3.x syntax" lines wrap theme={null}
  // Setting custom options for streaming mode
  LDConfig config = new LDConfig.Builder()
    .stream(true)
    .backgroundPollingIntervalMillis(120000)
    .build();

  // Specifying polling mode and setting custom polling options
  LDConfig config = new LDConfig.Builder()
    .stream(false)
    .pollingIntervalMillis(60000)
    .backgroundPollingIntervalMillis(120000)
    .build();
  ```

  ```java title="4.0 syntax" expandable lines wrap theme={null}
  // Setting custom options for streaming mode
  LDConfig config = new LDConfig.Builder()
    .dataSource(
      Components.streamingDataSource()
        .backgroundPollIntervalMillis(120000)
    )
    .build();

  // Specifying polling mode and setting custom polling options
  LDConfig config = new LDConfig.Builder()
    .dataSource(
      Components.pollingDataSource()
        .pollIntervalMillis(60000)
        .backgroundPollIntervalMillis(120000)
    )
    .build();
  ```
</CodeGroup>

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."

To learn more, read [`StreamingDataSourceBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/StreamingDataSourceBuilder.html) and [`PollingDataSourceBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/PollingDataSourceBuilder.html).

Another data source option is the test data source. To learn more, read the API documentation for [`com.launchdarkly.sdk.android.integrations.TestData`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/TestData.html).

### Understanding changes to events

Analytics events are enabled by default. To customize their behavior, call [`Components.sendEvents()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/Components.html#sendEvents\(\)) to get an [`EventProcessorBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.html) object with event-related options, and then pass that object to [`LDConfig.Builder.events()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html#events\(com.launchdarkly.sdk.android.subsystems.ComponentConfigurer\)).

To completely disable events, set [`LDConfig.Builder.events()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html#events\(com.launchdarkly.sdk.android.subsystems.ComponentConfigurer\)) to [`Components.noEvents()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/Components.html#noEvents\(\)). This was not possible in previous SDK versions.

Here is an example:

<CodeGroup>
  ```java title="3.x syntax" lines wrap theme={null}
  // Customizing event behavior
  LDConfig config = new LDConfig.Builder()
    .capacity(20000)
    .flushIntervalMillis(10000)
    .privateAttributes("email", "name", "myCustomAttribute")
    .build();

  // Disabling events is not possible in 3.x
  ```

  ```java title="4.0 syntax" lines wrap theme={null}
  // Customizing event behavior
  LDConfig config = new LDConfig.Builder()
    .events(
      Components.sendEvents()
        .capacity(20000)
        .flushIntervalMillis(10000)
        .privateAttributes("email", "name", "myCustomAttribute")
    )
    .build();

  // Disabling events
  LDConfig config = new LDConfig.Builder()
    .events(Components.noEvents())
    .build();
  ```
</CodeGroup>

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

[`EventProcessorBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.html) does not have methods that correspond to the previous configuration options `autoAliasingOptOut` and `inlineUsersInEvents`. Updates to the LaunchDarkly event model have made those options obsolete.

### Understanding changes to networking

Options in this category affect how the SDK communicates with LaunchDarkly over HTTP/HTTPS, such as connection timeout and custom headers. If you need to customize these, get an [`HttpConfigurationBuilder`](http://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/HttpConfigurationBuilder.html) object by calling [`Components.httpConfiguration()`](http://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/Components.html#httpConfiguration\(\)), configure it, and then pass it to [`LDConfig.Builder.http()`](http://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html#http\(com.launchdarkly.sdk.android.subsystems.ComponentConfigurer\)).

To learn more, read [`HttpConfigurationBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/HttpConfigurationBuilder.html).

Here is an example:

<CodeGroup>
  ```java title="3.x syntax" lines wrap theme={null}
  // Setting connection timeout
  LDConfig config = new LDConfig.Builder()
    .connectTimeoutMillis(3000)
    .build();
  ```

  ```java title="4.0 syntax" lines wrap theme={null}
  // Setting connection timeout
  LDConfig config = new LDConfig.Builder()
    .http(
      Components.httpConfiguration()
        .connectTimeoutMillis(3000)
    )
    .build();
  ```
</CodeGroup>

### Understanding changes to service URI configuration

Previous versions of the SDK had `pollUri`, `streamUri`, and `eventsUri` options for using custom service URIs instead of the standard LaunchDarkly service URIs. This is normally only necessary if you are using the LaunchDarkly Relay Proxy, or if your company uses a special LaunchDarkly domain that is not `launchdarkly.com`.

To customize these options in the new SDK, call [`Components.serviceEndpoints()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/Components.html#serviceEndpoints\(\)) to get a [`ServiceEndpointsBuilder`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/integrations/ServiceEndpointsBuilder.html) object, configure this builder, and then pass it to [`LDConfig.Builder.serviceEndpoints()`](https://launchdarkly.github.io/android-client-sdk/com/launchdarkly/sdk/android/LDConfig.Builder.html#serviceEndpoints\(com.launchdarkly.sdk.android.integrations.ServiceEndpointsBuilder\)).

Here is an example:

<CodeGroup>
  ```java title="3.x syntax, federal" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
    .pollUri(Uri.parse("https://clientsdk.launchdarkly.us"))
    .streamUri(Uri.parse("https://clientstream.launchdarkly.us"))
    .eventsUri(Uri.parse("https://events.launchdarkly.us"))
    .build();
  ```

  ```java title="4.0 syntax, federal" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
    .serviceEndpoints(
      Components.serviceEndpoints()
        .polling("https://clientsdk.launchdarkly.us")
        .streaming("https://clientstream.launchdarkly.us")
        .events("https://events.launchdarkly.us")
    )
    .build();
  ```

  ```java title="3.x syntax, EU" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
    .pollUri(Uri.parse("https://clientsdk.eu.launchdarkly.com"))
    .streamUri(Uri.parse("https://clientstream.eu.launchdarkly.com"))
    .eventsUri(Uri.parse("https://events.eu.launchdarkly.com"))
    .build();
  ```

  ```java title="4.0 syntax, EU" lines wrap theme={null}
  LDConfig config = new LDConfig.Builder()
    .serviceEndpoints(
      Components.serviceEndpoints()
        .polling("https://clientsdk.eu.launchdarkly.com")
        .streaming("https://clientstream.eu.launchdarkly.com")
        .events("https://events.eu.launchdarkly.com")
    )
    .build();
  ```
</CodeGroup>

## Understanding what was deprecated

All types and methods that were marked as deprecated in the last 3.x release have been removed from the 4.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 deprecated types and methods, read the [release notes for 4.0.0](https://github.com/launchdarkly/android-client-sdk/releases/tag/4.0.0).
