Using Segment Analytics in your Java Servlet


Lately I’ve been working on a project using Apache Tomcat to serve a REST API, which is used by our client iOS application to interact with our back-end. Now that the app is available publicly, we want to gather data about how people use our service in order to analyze trends and make improvements to our product.

After doing some research on available solutions, I decided to use a service called Segment, which acts as a central hub for collecting your data before it’s forwarded to other analytics tools such as Flurry, Mixpanel, Amplitude and more. I needed a service that…

  1. is fast and easy to implement
  2. is not disruptive to the performance of our REST API
  3. allows integration with hundreds of other analytics tools without changing a line of code

… and Segment fits the bill… almost! Segment has a native Java library which uses it’s own internal queue to batch messages and send them asynchronously to their servers. It’s true that their Java library is easy to use… once it’s installed. In my case, it took several hours of trudging through Java Exceptions and Tomcat errors before I finally got it running. This was mainly due to the apparent lack of online resources for using Segment on server-side Java. In this post I will attempt to walk through the installation process and hopefully spare you readers the incredible frustration I faced this past week.
And with that, let’s begin!

Sign up for a Segment Account

First you will want to sign up for a Segment account here. Afterwards you need to create a Workspace and then a Project. Once you create your Project, you will be given a “Write Key.” You will need this key for connecting the Java library to your Segment account, so keep it handy.

Download the Java Library

The Segment Java library documentation can be found here. If you are working with Maven, you can add the following dependency to your pom.xml file.

<dependency>
    <groupId>com.segment.analytics.java</groupId>
    <artifactId>analytics</artifactId>
    <version>2.0.0-RC2</version>
</dependency>

For runtime, there are several JAR files you will need to place in your servlet WEB-INF/lib directory. For convenience, I’ve made a zip folder with all of them which you can download here.

Integrate Segment in your Code

I wrote the following class SegmentHelper.java which I use to maintain a singleton instance of the library’s Analytics class. Because the Java library uses an internal queue and batches messages, I don’t want to create a new instance every time an HTTP request hits my server. Using this helper class, I can reference a single instance from all of my other classes.

package com.myapp.helper;

import com.segment.analytics.Analytics;
import com.segment.analytics.Callback;
import com.segment.analytics.Log;
import com.segment.analytics.messages.Message;
import com.segment.analytics.messages.TrackMessage;
import com.segment.analytics.messages.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class SegmentHelper implements Callback {
    private static final String writeKey = "my_write_key_string";
    private static volatile Segment segment = new Segment();
    private Analytics analytics;

    private SegmentHelper(){
        try{
            this.analytics = Analytics.builder(writeKey).callback(this).build();
        }catch(Exception e){
            System.out.println("exception while creating Analytics : " + e);
        }
    }

    public static SegmentHelper getInstance(){
        return segment;
    }
 
    public Analytics getAnalyticsClient(){
        return segment.analytics;
    }

    public void success(Message message) {
        System.out.println("Successfully uploaded " + message);
    }

    public void failure(Message message, Throwable throwable) {
        System.out.println("Could not upload " + message);
    }

    public void addUser(String user_id, Map<String, Object> properties) {
        try {
            this.analytics.enqueue(IdentifyMessage.builder().userId(user_id).traits(properties));
            trackUser(user_id, "Logged In", properties);
        } catch (Exception e) {
            System.out.println("Exception in addUser() - " + e);
        }
    }

    public void trackUser(String user_id, String description, Map<String, Object> properties) {
        try {
            this.analytics.enqueue(TrackMessage.builder(description).userId(user_id).properties(properties));
        } catch (Exception e) {
           System.out.println("Exception in trackUser() - " + e);
        }
    }

    public void groupUser(String group_id, String user_id) {
        try {
            this.analytics.enqueue(GroupMessage.builder(group_id).userId(user_id));
        } catch (Exception e) {
            System.out.println("Exception in groupUser() - " + e);
        }
    }

    public static void main(String[] args){

    }
} 

I have included implementations for the three most common messages: Identify, Track and Group. In order to use Segment.class, simply grab the instance of Analytics using getInstance() and then call a function and pass in the necessary values. For instance…

package com.myapp.app;

import com.myapp.helper.SegmentHelper;
//other imports

@WebServlet("/Register/*")
public class Register extends HttpServlet {
	
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response)
				throws IOException, ServletException
	{
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
        JSONObject json = new JSONObject();
		try {
			String name = request.getParameter("name");
			if (name != null ) {
				String user_id = addNewUser(name, json);
				Map<String, Object> properties = new LinkedHashMap<>();
				properties.put("name", name);
				SegmentHelper segment = SegmentHelper.getInstance();
				segment.addUser(user_id, properties);
			} else {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				json.put("Error", "One or more required parameters is missing.");
			}
		} catch (org.json.JSONException e) {
				System.out.println("E in Register - " + e);
		}
		out.print(json.toString());
		out.close();
	}
	
    private String addNewUser(String name, JSONObject json) {
        String user_id;
        //do something here, give user_id a value
        return user_id;
    }
}

You can also just grab the instance of Analytics itself by using Segment.getAnalyticsClient() and use it as necessary in another class.

Something’s Wrong…

Maven: “diamond operator is not supported in -source 1.5”

You may see this error when you try to package your class files with maven. The solution is to use the maven-compiler-plugin to specify the source and target JVM version to use. In this case I am using JVM 1.8. Place the following in your pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Java NullPointerException in SegmentHelper

Make sure that you never assign a null value in your properties Map. Although Map does support null values, sending a Map with null values to Segment’s Java library will result in a NullPointerException.

Integration and Conclusion

Now that you are sending your data to Segment, it’s dead simple to add Integrations. Login to your Segment account dashboard, navigate to your project, and then click on the Integrations tab on the left where you can browse through all the services that are offered. When you find one you’d like to enable, click on it to see what settings are needed. In most cases, all you need to do is sign up for that service on their website, retrieve your API key and place it in the settings in Segment. Just like that, all the analytics data you are sending to Segment will automatically be forwarded to the other services you have enabled.

In retrospect, it IS a simple installation. I only wish that Segment’s documentation was more detailed regarding the Java library’s dependencies. They also don’t provide any examples of the implementation of their library in different contexts. However, all things considered, Segment is a fantastic service for collecting analytics data and integrating with other tools.

If this helped you, or if you have any suggestions, please comment below!


4 responses to “Using Segment Analytics in your Java Servlet”