For the REST-purists under us, a resources ending with a slash is not the same as a resource ending without a slash. However, the difference between url://xxxx.com/a and url://xxxx.com/a/ is ignored. The trailing slash is omitted.
When you try to map a resource with a trailing slash using the standard @Path annotation, the methods get mapped to the same endpoint causing an exception.
@RequestScoped
public class TestResource {
@Path("{path}")
@GET
public String test1(@PathParam("path") String path) {
return "test1";
}
@Path("{path}/")
@GET
public String test2(@PathParam("path") String path) {
return "test2";
}
}
Glassfish answers during the deployment with:
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 … These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.
In Glassfish which uses the Jersey under the hood, the slash is also ignored. But there is a workaround. You can use regex in the Path-annotation to map resources ending with a slash. In the example at the end, I use regex to map the paths to the methods. But there is something you should know about the path-matching algorithm. The mapping algorithm uses the following rules:
The JAX-RS specification has defined strict sorting and precedence rules for matching URI expressions and is based on a most specific match wins algorithm. The JAX-RS provider gathers up the set of deployed URI expressions and sorts them based on the following logic:
- The primary key of the sort is the number of literal characters in the full URI matching pattern. The sort is in descending order.
- The secondary key of the sort is the number of template expressions embedded within the pattern, i.e., {id} or {id : .+}. This sort is in descending order.
- The tertiary key of the sort is the number of nondefault template expressions. A default template expression is one that does not define a regular expression, i.e., {id}.
In the following example, test2 is found before test1 because the regex is longer! We check the trailing slash first. When it fails, it falls back to test1.
@RequestScoped
public class TestResource {
@Path("{test:.*}")
@GET
public String test1(@PathParam("test") String test) {
return "test1";
}
@Path("{test:.*[/]}") // takes precedence
@GET
public String test2(@PathParam("test") String test) {
return "test2";
}
}