Close

JAX-RS - Sub Resource Locators

[Last Updated: Nov 22, 2017]

In JAX-RS, if a resource method is not annotated with request method (verb) designator, such as @GET or @POST etc, then it may return the instance of an object which will be used to handle the request. Such methods are called sub-resource locators. Let's understand that with an example.

Example

In following resource class, the method getOrderById() is a resource locator:

@Path("/")
public class OrderInfo {

  @Path("/orders/{id}")
  public OrderDetails getOrderById(@PathParam("id") int orderId) {
      return new OrderDetails(orderId);
  }
}
public class OrderDetails {

  private final int id;

  public OrderDetails(int id) {
      this.id = id;
  }

  @GET
  public String getOrderDetails() {
      return "test order details for order id: " + id;
  }
}

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

mvn tomcat7:run-war

The client

public class OrderClient {
  public static void main(String[] args) {
      Client client = ClientBuilder.newClient();
      WebTarget target =
              client.target("http://localhost:8080/orders/234");
      Response response = target.request()
                                .get();
      System.out.printf("status: %s%n", response.getStatus());
      System.out.printf("-- response body --%n%s%n", response.readEntity(String.class));
  }
}

Output

status: 200
-- response body --
test order details for order id: 234

Any object returned from resource locator is treated as a resource class instance and used to either handle the request or to further resolve the object that will handle the request. Sub-resource locators may have all the same method parameters as a normal resource method except that they MUST NOT have entity parameter. Also, the target object can have any resource annotations including @Path (to extend the resource path further).

Dynamic resource resolution

An application can use this feature to dynamically decide at runtime how to dispatch the request. The declared return type of a resource locator method can be an interface, that means during runtime the returned instance can be any polymorphic type.

Ambiguities resolution

When the resources are loaded via 'sub-resource locator', the JAX-RS runtime will not report ambiguities until during the runtime (until the target request is made) as opposed to the startup validation for statically declared resources. Following example demonstrates that.

@Path("/")
public class OrderInfo {

  @Path("/orders/{id}")
  public OrderDetails getOrderById(@PathParam("id") int orderId) {
      return new OrderDetails(orderId);
  }
}

Following resource class has the two methods having same path and both with @GET, hence are ambiguous:

public class OrderDetails {

  private final int id;

  public OrderDetails(int id) {
      this.id = id;
  }

  @GET
  public String getOrderDetails() {
      return "test order details for order id: " + id;
  }

  @GET
  public String getOrderDetails2() {
      return "test order details 2 for order id: " + id;
  }
}
public class OrderClient {
  public static void main(String[] args) {
      Client client = ClientBuilder.newClient();
      WebTarget target =
              client.target("http://localhost:8080/orders/234" );
      Response response = target.request()
                                .get();
      System.out.printf("status: %s%n", response.getStatus());
      System.out.printf("-- response body --%n%s%n", response.readEntity(String.class));
  }
}

Output

status: 500
-- response body --
<html><head><title>Apache Tomcat/7.0.47 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 500 - jersey.repackaged.com.google.common.util.concurrent.UncheckedExecutionException: org.glassfish.jersey.server.model.ModelValidationException: Model validation error(s) found in sub resource returned by sub resource locator.</h1><HR size="1" noshade="noshade"><p><b>type</b> Exception report</p><p><b>message</b> <u>jersey.repackaged.com.google.common.util.concurrent.UncheckedExecutionException: org.glassfish.jersey.server.model.ModelValidationException: Model validation error(s) found in sub resource returned by sub resource locator.</u></p><p><b>description</b> <u>The server encountered an internal error that prevented it from fulfilling this request.</u></p><p><b>exception</b> <pre>javax.servlet.ServletException: jersey.repackaged.com.google.common.util.concurrent.UncheckedExecutionException: org.glassfish.jersey.server.model.ModelValidationException: Model validation error(s) found in sub resource returned by sub resource locator.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails() and public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails2() at matching regular expression null. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@6e1e07e8']
org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:489)
org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
</pre></p><p><b>root cause</b> <pre>jersey.repackaged.com.google.common.util.concurrent.UncheckedExecutionException: org.glassfish.jersey.server.model.ModelValidationException: Model validation error(s) found in sub resource returned by sub resource locator.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails() and public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails2() at matching regular expression null. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@6e1e07e8']
jersey.repackaged.com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2203)
jersey.repackaged.com.google.common.cache.LocalCache.get(LocalCache.java:3937)
jersey.repackaged.com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
jersey.repackaged.com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.getRouting(RuntimeLocatorModelBuilder.java:176)
org.glassfish.jersey.server.internal.routing.SubResourceLocatorRouter.apply(SubResourceLocatorRouter.java:137)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:109)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:92)
org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:61)
org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:318)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
org.glassfish.jersey.internal.Errors.process(Errors.java:315)
org.glassfish.jersey.internal.Errors.process(Errors.java:297)
org.glassfish.jersey.internal.Errors.process(Errors.java:267)
org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
</pre></p><p><b>root cause</b> <pre>org.glassfish.jersey.server.model.ModelValidationException: Model validation error(s) found in sub resource returned by sub resource locator.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails() and public java.lang.String com.logicbig.example.OrderDetails.getOrderDetails2() at matching regular expression null. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@6e1e07e8']
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder$2.run(RuntimeLocatorModelBuilder.java:250)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
org.glassfish.jersey.internal.Errors.process(Errors.java:315)
org.glassfish.jersey.internal.Errors.process(Errors.java:297)
org.glassfish.jersey.internal.Errors.process(Errors.java:267)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.validateResource(RuntimeLocatorModelBuilder.java:242)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.buildRouting(RuntimeLocatorModelBuilder.java:229)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.createRouting(RuntimeLocatorModelBuilder.java:221)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.access$100(RuntimeLocatorModelBuilder.java:77)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder$1.load(RuntimeLocatorModelBuilder.java:153)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder$1.load(RuntimeLocatorModelBuilder.java:150)
jersey.repackaged.com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
jersey.repackaged.com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2319)
jersey.repackaged.com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2282)
jersey.repackaged.com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2197)
jersey.repackaged.com.google.common.cache.LocalCache.get(LocalCache.java:3937)
jersey.repackaged.com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
jersey.repackaged.com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
org.glassfish.jersey.server.internal.routing.RuntimeLocatorModelBuilder.getRouting(RuntimeLocatorModelBuilder.java:176)
org.glassfish.jersey.server.internal.routing.SubResourceLocatorRouter.apply(SubResourceLocatorRouter.java:137)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:109)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:92)
org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:61)
org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:318)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
org.glassfish.jersey.internal.Errors.process(Errors.java:315)
org.glassfish.jersey.internal.Errors.process(Errors.java:297)
org.glassfish.jersey.internal.Errors.process(Errors.java:267)
org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
</pre></p><p><b>note</b> <u>The full stack trace of the root cause is available in the Apache Tomcat/7.0.47 logs.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.47</h3></body></html>

We will see the similar exception logs on server console.

Let's see an example where direct resource methods (not resource locator) have ambiguity:

@Path("/")
public class OrderInfo {

  @Path("/orders2/{id}")
  @GET
  public String getOrderById(@PathParam("id") int orderId) {
       return "test order details for order id: " + orderId;
  }

  @Path("/orders2/{id}")
  @GET
  public String getOrderById2(@PathParam("id") int orderId) {
      return "test order details 2 for order id: " + orderId;
  }
}

Here we will have the exception during server startup:

Output


Jun 13, 2017 8:34:07 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
Jun 13, 2017 8:34:07 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
Jun 13, 2017 8:34:07 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/7.0.47
Jun 13, 2017 8:34:09 PM org.apache.catalina.util.SessionIdGenerator createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [112] milliseconds.
Jun 13, 2017 8:34:09 PM org.glassfish.jersey.internal.Errors logErrors
SEVERE: Following issues have been detected:
WARNING: A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderInfo.getOrderById(int) and public java.lang.String com.logicbig.example.OrderInfo.getOrderById2(int) at matching regular expression /orders2/([^/]+). These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.

Jun 13, 2017 8:34:09 PM org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderInfo.getOrderById(int) and public java.lang.String com.logicbig.example.OrderInfo.getOrderById2(int) at matching regular expression /orders2/([^/]+). These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@29693a5f']
	at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:555)
	at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
	at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
	at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
	at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
	at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
	at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:392)
	at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:177)
	at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:369)
	at javax.servlet.GenericServlet.init(GenericServlet.java:160)
	at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1280)
	at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1091)
	at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5176)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5460)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Jun 13, 2017 8:34:09 PM org.apache.catalina.core.StandardContext loadOnStartup
SEVERE: Servlet  threw load() exception
org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public java.lang.String com.logicbig.example.OrderInfo.getOrderById(int) and public java.lang.String com.logicbig.example.OrderInfo.getOrderById2(int) at matching regular expression /orders2/([^/]+). These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@29693a5f']
	at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:555)
	at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
	at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
	at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
	at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
	at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
	at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:392)
	at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:177)
	at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:369)
	at javax.servlet.GenericServlet.init(GenericServlet.java:160)
	at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1280)
	at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1091)
	at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5176)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5460)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Jun 13, 2017 8:34:10 PM org.apache.coyote.AbstractProtocol start
    INFO: Starting ProtocolHandler ["http-bio-8080"]

Example Project

Dependencies and Technologies Used:

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

Sub Resuorce Locator Example Select All Download
  • sub-resource-locators
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • OrderInfo.java

    See Also