Java SDK

Use RudderStack’s Java SDK to send server-side events to various destinations.

RudderStack’s Java SDK lets you track and send the events from your Java applications to the specified destinations.

Refer to the SDK’s GitHub codebase for the implementation-specific details.

SDK setup requirements

  1. Sign up to RudderStack Cloud.
  2. Set up a Java source in your dashboard. You should be able to see a write key for this source:
Java source write key

You will also need a data plane URL. Refer to the Dashboard Overview guide for more information on the data plane URL and where to find it.

success
The Setup tab in the RudderStack dashboard (seen above) has the SDK installation snippet containing both the write key and the data plane URL. Copy it to integrate the Java SDK into your application.

Installing the Java SDK

warning
As Bintray has sunset from 1st May, 2021, the Java SDK is now moved to Maven Central. All the versions from 1.0.1 will now be available in Maven Central only.

It is highly recommended to use the Maven build system to add the SDK to your project.

To install the RudderStack Java SDK, add the following lines of code to pom.xml:

<dependency>
   <groupId>com.rudderstack.sdk.java.analytics</groupId>
     <artifactId>analytics</artifactId>
   <version>3.0.0</version>
</dependency>

If you’re using Gradle, add the following line to your dependencies:

implementation 'com.rudderstack.sdk.java.analytics:analytics:3.0.0'

Initializing the RudderStack client

After installing the SDK, run the following code snippet to initialize the RudderStack client:

RudderAnalytics analytics = RudderAnalytics
         .builder("<WRITE_KEY>")
         .setDataPlaneUrl("<DATA_PLANE_URL>")
         .build();

Migrating from v2 to v3

To migrate to the Java SDK v3.0.0, set the data plane URL using setDataPlaneUrl("<DATA_PLANE_URL>") (as seen in the above section) instead of passing it as an argument.

Configuring the RudderStack client

You can configure your client based on the following methods in RudderClient.Builder:

MethodTypeDescription
clientOkHttpClientSets a custom OkHttpClient. It is created by default.
setGZIPBooleanGzips the event request.

Default value: true
logLogSets the logging level for debugging. Available options are VERBOSE, DEBUG, ERROR, and NONE.

Default value: NONE
setDataPlaneUrlStringSets the data plane URL.

Default value: https://hosted.rudderlabs.com
setUploadURLStringSets the data plane URL - used for Segment compatibility.

Default value: https://hosted.rudderlabs.com
userAgentStringSets a user agent for the HTTP requests.

Default value: analytics-java/{analytics-sdk-version}
queueCapacityIntegerSets the queue capacity.

Default value: Integer.MAX_VALUE
retriesIntegerDefines the maximum number of event retries.

Default value: 3
networkExecutorExecutorServiceSets the executor service on which all HTTP requests are made.

Default value: SingleThreadExecutor
callbackCallbackGets invoked when the client library processes an event.

Default value: Empty list.
forceTlsVersion1-Enforces TLS v1.

Default value: false

The following initialization methods are currently in beta:

Available methodTypeDescription
messageTransformerMessageTransformerAdds a transformer for the message before uploading it.

Default value: null
messageInterceptorMessageInterceptorAdd a MessageInterceptor for intercepting messages before sending to RudderStack.

Default value: null
flushQueueSizeIntegerSets the queue size at which the SDK triggers the flush requests.

Default value: 250
maximumQueueSizeInBytesIntegerSets the maximum queue size at which the flush requests are triggered.

Default value: 1024*500 Bytes
flushIntervalLong, TimeUnitSets the time interval which the SDK flushes the queue.

Default value: 10 seconds
threadFactoryThreadFactorySets the thread factory used to create the threads.

Default value: null
pluginPluginUsed to configure the builder.

Default value: null

Sending events

warning
RudderStack does not store or persist the user state in any of the server-side SDKs.

Unlike the client-side SDKs that deal with only a single user at a given time, the server-side SDKs deal with multiple users simultaneously. Therefore, you must specify either the userId or anonymousId every time while making any API calls supported by the Java SDK.

Identify

The identify call lets you identify a visiting user and associate them to their actions. It also lets you record the traits about them like their name, email address, etc.

A sample identify call made using the Java SDK is shown below:

analytics.enqueue(IdentifyMessage.builder()
    .userId("1hKOmRA4GRlm")
    .traits(ImmutableMap.builder()
        .put("name", "Alex Keener")
        .put("email", "alex@example.com")
        .build()
    )
);

The identify method parameters are as described below:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.
traitsObjectAn optional dictionary of the user’s traits like name or email.

Track

The track call lets you record the user actions along with their associated properties. Each user action is called an event.

A sample track call is shown below:

Map<String, Object> properties = new LinkedHashMap<>();
    properties.put("key1", "value1");
    properties.put("key2", "value2");
    analytics.enqueue(
       TrackMessage.builder("Java Test")
           .properties(properties)
           .anonymousId(anonymousId)
           .userId(userId)
);

The track method parameters are as described below:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
event
Required
StringName of the event.
propertiesObjectAn optional dictionary of the properties associated with the event.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.

Page

The page call lets you record the page views on your application along with the other relevant information about the page.

A sample page call is as shown:

analytics.enqueue(PageMessage.builder("Schedule")
    .userId("1hKOmRA4GRlm")
    .properties(ImmutableMap.builder()
        .put("category", "Cultural")
        .put("path", "/a/b")
        .build()
    )
);

The page method parameters are as described below:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
name
Required
StringName of the viewed page.
propertiesObjectAn optional dictionary of the properties associated with the viewed page, like url or referrer.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.

Screen

The screen call is the mobile equivalent of the page call. It lets you record the screen views on your mobile app along with other relevant information about the screen.

A sample screen call is as shown:

analytics.enqueue(ScreenMessage.builder("Schedule")
    .userId("1hKOmRA4GRlm")
    .properties(ImmutableMap.builder()
        .put("category", "Sports")
        .put("path", "/sports/schedule")
        .build()
    )
);

The screen method parameters are as described below:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
name
Required
StringName of the viewed screen.
propertiesObjectAn optional dictionary of the properties associated with the screen, like url or referrer.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.

Group

The group call lets you link an identified user with a group, such as a company, organization, or an account. It also lets you record any custom traits or properties associated with that group.

A sample group call made using the Java SDK is shown below:

analytics.enqueue(GroupMessage.builder("group123")
    .userId("1hKOmRA4GRlm")
    .traits(ImmutableMap.builder()
        .put("name", "Rudder")
        .put("size", 19)
        .build()
    )
);

The group method parameters are as follows:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
groupId
Required
StringUnique identifier of the group in your database.
traitsObjectAn optional dictionary of the group’s traits like nameor email.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.

Alias

The alias call lets you merge different identities of a known user. It is an advanced method that lets you change the tracked user’s ID explicitly. You can use alias for managing the user’s identity in some of the downstream destinations.

warning
RudderStack supports sending alias events only to select downstream destinations. Refer to the destination-specific documentation for more details.

A sample alias call is as shown:

analytics.enqueue(AliasMessage.builder("previousId")
    .userId("newId")
);

The alias method parameters are as mentioned below:

FieldTypeDescription
userId
Required, if anonymousId is absent.
StringUnique identifier for a user in your database.
anonymousId
Required, if userId is absent.
StringUse this field to set an identifier in cases where there is no unique user identifier.
previousId
Required
StringThe previous unique identifier of the user.
traitsObjectAn optional dictionary of the user’s traits like name or email.
contextObjectAn optional dictionary of information that provides context about the event. It is not directly related to the API call.
integrationsObjectAn optional dictionary containing the destinations to be enabled or disabled.
timestampTimestamp in ISO 8601 formatThe timestamp of the event’s arrival.

Filtering destinations

The Java SDK lets you enable or disable sending events to specifc destinations connected to the source. You can do so by passing the integrations object in your API calls:

analytics.enqueue(TrackMessage.builder("Button Clicked")
    .userId("1hKOmRA4GRlm")
    .enableIntegration("All", false)
    .enableIntegration("Amplitude", true)
);

The above snippet disables sending the event Button Clicked to any destination except Amplitude.

warning
The destination flags are case sensitive. They should match the destination’s name as specified in the RudderStack dashboard.

Context

With the Java SDK, you can send contextual information about the event using the context object:

analytics.enqueue(TrackMessage.builder("Button Clicked")
    .userId("1hKOmRA4GRlm")
    .context(ImmutableMap.builder()
        .put("ip", "1.23.45.67")
        .put("language", "en-uk")
        .build()
    )
);

The Java SDK also adds the information present in context.library with every message like name, version, etc.

A sample context object containing the library information is shown below:

"context": {
	"library": {
		"name": "analytics-java",
		"version": "x.x.x"
	}
}

If you pass any custom information in the context object, the SDK automatically merges it with the existing context, except the information contained in library.

Batching events

The RudderStack SDKs are built to support high performance environments. It is safe to use the Java SDK on a web server serving hundreds of requests per second.

Every SDK API you call does not result in a HTTP request but it is queued in the memory instead. RudderStack flushes the events in batches in the background, allowing faster operations.

The Java SDK has a maximum size limit of 500KB per batch request and 32KB per call.

warning
The RudderStack HTTP Tracking API accepts batch requests upto 500KB. To avoid any errors while sending the event requests, make sure the single event payload size is below 32KB.

Flushing events

To flush your events, the Java SDK supports the flush method. It notifies the RudderStack client to upload the events and make sure no events are left in the queue at any given point.

A sample snippet highlighting the use of the flush method is shown below:

analytics.flush()

Blocking flush

By default, the Java SDK does not support blocking flush implicitly. You need to create a BlockingFlush class (handles a maximum of 65535 parallel calls to flush) or a TierBlockingFlush class (no limit on parallel calls) depending on your requirement.

warning
Both BlockingFlush and TierBlockingFlush classes are not a part of the core Java SDK.

A sample snippet highlighting the use of BlockingFlush is shown below:

final BlockingFlush blockingFlush = BlockingFlush.create();

RudderAnalytics analytics = RudderAnalytics
         .builder("<WRITE_KEY>")
		 .plugin(blockingFlush.plugin())
         .setDataPlaneUrl("<DATA_PLANE_URL>")
         .build();

// ...YOUR CODE...

analytics.flush(); // Triggers a flush.
blockingFlush.block();
analytics.shutdown(); // Shuts down after the flush is complete.

A detailed implementation of the BlockingFlush class is shown below. Note that this is just a sample code snippet and you can modify it as per your use case.

package sample;

import com.rudderstack.sdk.java.analytics.RudderAnalytics;
import com.rudderstack.sdk.java.analytics.Callback;
import com.rudderstack.sdk.java.analytics.MessageTransformer;
import com.rudderstack.sdk.java.analytics.Plugin;
import com.rudderstack.sdk.java.analytics.messages.Message;
import com.rudderstack.sdk.java.analytics.messages.MessageBuilder;
import java.util.concurrent.Phaser;

/*
 * The {@link RudderAnalytics} class doesn't come with a blocking {@link RudderAnalytics#flush()} implementation
 * out of the box. It's trivial to build one using a {@link Phaser} that monitors requests and is
 * able to block until they're uploaded.
 */
public class BlockingFlush {

  public static BlockingFlush create() {
    return new BlockingFlush();
  }

  BlockingFlush() {
    this.phaser = new Phaser(1);
  }

  final Phaser phaser;

  public Plugin plugin() {
    return builder -> {
      builder.messageTransformer(
              builder1 -> {
                phaser.register();
                return true;
              });

      builder.callback(
          new Callback() {
            @Override
            public void success(Message message) {
              phaser.arrive();
            }

            @Override
            public void failure(Message message, Throwable throwable) {
              phaser.arrive();
            }
          });
    };
  }

  public void block() {
    phaser.arriveAndAwaitAdvance();
  }
}
warning
The above implementation restricts the maximum number of parties to 65535. If you try to create and use more parties, this class throws an error. To remove this limitation and use more parties, refer to the TierBlockingFlush section below.

TierBlockingFlush

To remove the limitations on the maximum number of supported parties, you can use the TierBlockingFlush class.

The following snippet highlights its use:

final TierBlockingFlush blockingFlush = TierBlockingFlush.create();

RudderAnalytics analytics = RudderAnalytics
         .builder("<WRITE_KEY>")
		 .plugin(blockingFlush.plugin())
         .setDataPlaneUrl("<DATA_PLANE_URL>")
         .build();

// ...YOUR CODE...

analytics.flush(); // Trigger a flush.
blockingFlush.block();
analytics.shutdown(); // Shut down after the flush is complete.

The following snippet highlights a detailed implementation of the TierBlockingFlush class with support for more than 65535 parties. Note that this is just a sample code snippet and you can modify it as per your use case.

package sample;

import com.rudderstack.sdk.java.analytics.Callback;
import com.rudderstack.sdk.java.analytics.Plugin;
import com.rudderstack.sdk.java.analytics.messages.Message;

import java.util.concurrent.Phaser;

/**
 * Blocking flush implementor for cases where parties exceed 65535
 */
public class TierBlockingFlush {

    private static final int MAX_PARTIES_PER_PHASER = (1 << 16) - 2; // max a phaser can accommodate

    public static TierBlockingFlush create() {
        return new TierBlockingFlush(MAX_PARTIES_PER_PHASER);
    }

    private TierBlockingFlush(int maxPartiesPerPhaser) {
        this.currentPhaser = new Phaser(1);
        this.maxPartiesPerPhaser = maxPartiesPerPhaser;
    }

    private Phaser currentPhaser;
    private final int maxPartiesPerPhaser;

    public Plugin plugin() {
        return builder -> {
            builder.messageTransformer(
                    messageTransformationBuilder -> {
                        currentPhaser = currentPhaser.getRegisteredParties() == maxPartiesPerPhaser ? new Phaser(currentPhaser) : currentPhaser;
                        currentPhaser.register();
                        return true;
                    });

            builder.callback(
                    new Callback() {
                        @Override
                        public void success(Message message) {
                            onResult();
                        }

                        @Override
                        public void failure(Message message, Throwable throwable) {
                            onResult();
                        }

                        private void onResult() {
                            if (currentPhaser.getUnarrivedParties() == 0) {
                                currentPhaser = currentPhaser.getParent();
                            }
                            currentPhaser.arrive();
                        }
                    });
        };
    }

    public void block() {
        currentPhaser.arriveAndAwaitAdvance();
    }
}

Logging

To see the data that is sent over HTTP when debugging any issues, enable the SDK’s verbose logging feature.

  • Refer to the sample snippet for more information on setting the logs using the Java SDK.
  • Refer to the sample app for more information on using the logging plugin during the SDK initialization.

Gzipping requests

success
The Gzip feature is enabled by default in the Java SDK version 3.0.0.

The Java SDK automatically gzips requests. It also lets you do so using interceptors in OkHttp.

info
Refer to the sample app in the Java SDK repository for a working example.

To disable the Gzip feature using the setGZIP API while initializing the SDK, run the following snippet:

RudderAnalytics analytics = RudderAnalytics
         .builder("<WRITE_KEY>")
         .setDataPlaneUrl("<DATA_PLANE_URL>")
		 .setGZIP(false)
         .build();

Note that if you pass the OkHttp client using the client API while initializing your SDK, then it is preferred over the default Gzip behavior. It means that even if you use the setGZIP API to enable/disable Gzip requests, the behavior will be determined based on the interceptor passed in the OkHttp client.

warning
To gzip requests on a self-hosted data plane, make sure your rudder-server version is 1.4 or higher. Otherwise, your events might fail.

FAQ

Can I use the ImmutableMap class?

Yes, you can use the ImmutableMap class via the Guava library or use the Java maps.

How do I flush events on demand?

To flush your events on demand, call the flush method as shown:

analytics.flush()

How does the Java SDK handle events larger than 32KB?

The Java SDK accepts and sends each event greater than 32KB as a single batch and sends them to the backend.

Does the Java SDK support event ordering?

The Java SDK does not support event ordering by default.



Questions? Contact us by email or on Slack