So after some dabbling around with some of the answers in Optional @PathParam in Jax-RS, the problem is that using this
@Path("/myMethod{id: (/\\d+)?}")
public Response get(@PathParam("id") int id) {}
causes the /
to be in the capture group. So when Jersey tries to parse /1
it will get an exception and send a 404. We could use a String, but then it gets ugly, as we need to get rid of the leading /
and parse it ourselves.
@Path("/myMethod{id: (/\\d+)?}")
public Response get(@PathParam("id") String id) {
id = id.replace("/", "");
int parsed = Integer.parseInt(id);
}
The other solution I came up with (the one that works for the OP), is to separate the /
from the digits into two different path expressions, so that the leading /
is not captured in the actual id and doesn't fail in parsing
@Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
public Response get(@PathParam("id") int id) {}
The {noop: (/)?}
captures the optional /
. And the {id: ((?<=/)\\d+)?}
uses a positive lookbehind, saying that the numbers (\\d+
) are allowed if and only if there is a /
before it ((?<=/)
). This is necessary as the /
is optional. If we didn't use this assertion, then /myMethod123
would be allowed.
Here is a complete test case using Jersey Test Framework
public class OptionalParamTest extends JerseyTest {
@Path("optional")
public static class Resource {
@GET
@Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
public String get(@PathParam("id") int id) {
return String.valueOf(id);
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(Resource.class);
}
@Test
public void should_return_id_1() {
Response response = target("optional/method/1").request().get();
System.out.println("status=" + response.getStatus());
assertEquals("1", response.readEntity(String.class));
}
@Test
public void should_return_id_0_with_no_id() {
Response response = target("optional/method").request().get();
assertEquals(200, response.getStatus());
assertEquals("0", response.readEntity(String.class));
}
@Test
public void should_return_404_with_numbers_and_no_slash() {
Response response = target("optional/method12").request().get();
assertEquals(404, response.getStatus());
}
@Test
public void should_return_404_with_numbers_and_letters() {
Response response = target("optional/method/12b").request().get();
assertEquals(404, response.getStatus());
}
@Test
public void should_return_404_with_only_letters() {
Response response = target("optional/method/ab").request().get();
assertEquals(404, response.getStatus());
}
}
Here's the dependency for the test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
EDIT
For the tests, it would be better to use a boxed Integer
instead of an int
as the method parameter. With the former you would be able to do a null check, instead of receiving the default 0
for the primitive.