Instance-per-request vs Singleton resources in JAX-RS

[Updated: Apr 20, 2017, Created: Apr 19, 2017]

By default a new instance of the resource class is created for each new request.

We can change this default by overriding Application#getSingletons method in our Application implementation. In that case the same instances of the specified singleton classes will be used for all corresponding requests.

For singleton resources, we have to make sure of thread-safety, as multiple request coming in different threads can interfere each other when operating on shared data.

Let's understand that with examples.

The Application implementation

@ApplicationPath("/")
public class MyRestApp extends Application {

  @Override
  public Set<Class<?>> getClasses() {
      Set<Class<?>> set = new HashSet<>();
      set.add(MyResource.class);
      return set;
  }

  @Override
  public Set<Object> getSingletons() {
      Set<Object> set = new HashSet<>();
      set.add(new MySingletonResource());
      return set;
  }
}

MyResource.java

@Path("/")
public class MyResource {
  private int counter;

  @GET
  @Path("count1")
  public void count() {
       counter++;
  }

  @GET
  @Path("counter1")
  public int getCounter() {
      return counter;
  }

  @GET
  @Path("reset1")
  public void reset() {
      counter = 0;
  }
}

MySingletonResource.java

@Path("/")
public class MySingletonResource {
  private int counter;

  @GET
  @Path("count2")
  public void count() {
      counter++;
  }

  @GET
  @Path("counter2")
  public int getCounter() {
      return counter;
  }

  @GET
  @Path("reset2")
  public void reset() {
      counter = 0;
  }
}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run

The resource client

Let's write a client using JAX-RS client API.

public class MyClient1 {
  public static void main(String[] args) {
      //default resource
      getRequest("/reset1", Void.class);
      countRequest("/count1");
      Integer counter = getRequest("/counter1", Integer.class);
      System.out.printf("counter1: %s%n", counter);

      //singleton resource
      getRequest("/reset2", Void.class);
      countRequest("/count2");
      Integer counter2 = getRequest("/counter2", Integer.class);
      System.out.printf("counter2: %s%n", counter2);
  }

  public static void countRequest(String uri) {
      for (int i = 0; i < 10; i++) {
          getRequest(uri, Void.class);
      }
  }
  
  public static <T> T getRequest(String uri, Class<T> responseType) {
      Client client = ClientBuilder.newClient();
      WebTarget target = client.target("http://localhost:8080" + uri);
      return target.request().get(responseType);
  }
}

Output

counter1: 0
counter2: 10

Above output shows a single instance of the singleton resource is used for multiple requests, hence increasing the counter value gradually.

Singleton Resources and thread safety

Let's write a client for our singleton resource which will make multiple requests in multiple threads simultaneously.

public class MyClient2 {

  public static void main(String[] args) {
      MyClient1.getRequest("/reset2", Void.class);
      multiThreadedCountRequest("/count2");
      Integer counter = MyClient1.getRequest("/counter2", Integer.class);
      System.out.printf("counter2: %s%n", counter);
  }

  public static void multiThreadedCountRequest(String s) {
      final ExecutorService es = Executors.newFixedThreadPool(1000);
      for (int i = 0; i < 1000; i++) {
          es.execute(() -> MyClient1.getRequest(s, Void.class));
      }
      es.shutdown();
      try {
          es.awaitTermination(5, TimeUnit.MINUTES);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      }

  }
}

Output

counter2: 991

We were expecting counter value of 1000 but instead got 991. On multiple runs, we will get different results. That happens because of the thread interference.

Let's write a thread safe counter resource:

@Path("/")
public class MySingletonResource2 {
  private AtomicInteger counter = new AtomicInteger(0);

  @GET
  @Path("count3")
  public void count() {
      counter.incrementAndGet();
  }

  @GET
  @Path("counter3")
  public int getCounter() {
      return counter.get();
  }

  @GET
  @Path("reset3")
  public void reset() {
      counter.set(0);
  }
}
public class MyClient3 {
  public static void main(String[] args) {
      MyClient1.getRequest("/reset3", Void.class);
      MyClient2.multiThreadedCountRequest("/count3");
      Integer counter = MyClient1.getRequest("/counter3", Integer.class);
      System.out.printf("counter3: %s%n", counter);
  }
}

Output

counter3: 1000

Here we conclude that multiple overlapping requests for singleton resource may interfere each other while operating on share data. If we opt to use singleton resources, we have to make sure of thread safety ourselves.

Example Project

Dependencies and Technologies Used :

  • jersey-container-servlet 2.25.1: Jersey core Servlet 3.x implementation.
  • JDK 1.8
  • Maven 3.3.9

Resource Lifecyle Examples Select All Download
  • jaxrs-resource-lifecycle
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also