Menu

Nakov.com logo

Thoughts on Software Engineering

JAX-RS, @Path, @PathParam and Optional Parameters

In a recent Java project I needed to develop and provide to external clients a RESTful Web Services interface to an internal system. After some research I found that using JAX-RS and its open-source implementation Jersey with Spring and Hibernate as back-end will be great technologies stack for this project. Seems easy but unfortunately I found that JAX-RS does not support optional path parameters.

Customer requested each service to have an optional path parameter called “format” that specifies the output format. All services were required to support multiple output formats: XML, plain text, CSV, JSON, PDF, etc. For example if I request this URL: http://myserver.com/services/location/3/format/xml, the output should be XML, but if I request just http://myserver.com/services/location/3 without “format” parameter, the result should be plain text.

Mandatory Path Parameters

Using a path pattern like this:

@Path("users/{userId}/format/{format}")

makes the parameter “format” mandatory. If we skip it, the request will not match the path.

Optional @Path Parameters in JAX-RS

Using regular expressions and a simple hack can overcome this limitation in JAX-RS. The following example defines two optional path parameters “format” and “encoding”:

@GET
@Path("/user/{id}{format:(/format/[^/]+?)?}{encoding:(/encoding/[^/]+?)?}")
public Response getUser(
  @PathParam("id") int id,
  @PathParam("format") String format,
  @PathParam("encoding") String encoding) {
 String responseText = "";

if (format.equals("")) {
  // Optional parameter "format" not specified
  responseText += "No format specified.";
 } else {
  // Optional parameter "format" has looks like "/format/pdf" -> get it's value only
  format = format.split("/")[2];
  responseText += "Format=" + format;
 }

if (encoding.equals("")) {
  // Optional parameter "encoding" not specified
  responseText += " No encoding specified";
 } else {
  // Optional parameter "encoding" has looks like "/encoding/utf8" -> get it's value only
  encoding = encoding.split("/")[2];
  responseText += " Encoding=" + encoding;
 }

return Response.status(200).type("text/plain").entity(responseText).build();
}

Requesting http://localhost:8080/services/user/3, will return “No format specified. No encoding specified”.
Requesting http://localhost:8080/services/user/3/format/pdf/encoding/utf8, will return “Format=pdf Encoding=utf8”.
Requesting http://localhost:8080/services/user/3/encoding/utf8, will return “No format specified. Encoding=utf8”.

Flexible @Path Parameters in JAX-RS

If we need more flexibility, we can match the entire path ending in the REST request and map it in key-value style (HashMap< String, String >):

@GET
@Produces({"application/xml", "application/json", "plain/text"})
@Path("/location/{locationId}{path:.*}")
public Response getLocation(
  @PathParam("locationId") int locationId,
  @PathParam("path") String path) {
 Map&lt; String, String&gt; params = parsePath(path);
 String format = params.get("format");
 if ("xml".equals(format)) {
  String xml = "<location></location><id></id>" + locationId + "";
  return Response.status(200).type("application/xml").entity(xml).build();
 } else if ("json".equals(format)) {
  String json = "{ 'location' : { 'id' : '" + locationId + "' } }";
  return Response.status(200).type("application/json").entity(json).build();
 } else {
  String text = "Location: id=" + locationId;
  return Response.status(200).type("text/plain").entity(text).build();
 }
}

private Map&lt; String, String &gt; parsePath(String path) {
 if (path.startsWith("/")) {
  path = path.substring(1);
 }
 String[] pathParts = path.split("/");
 Map&lt; String, String &gt; pathMap = new HashMap&lt; String, String &gt;();
 for (int i=0; i &lt; pathParts.length/2; i++) {
  String key = pathParts[2*i];
  String value = pathParts[2*i+1];
  pathMap.put(key, value);
 }
 return pathMap;
}

Requesting http://localhost:8080/services/location/3, will return “Location: id=3”.
Requesting http://localhost:8080/services/location/3/format/json, will return “{ ‘location’ : { ‘id’ : ‘3’ } }”.

Enjoy!

Comments (25)

25 Responses to “JAX-RS, @Path, @PathParam and Optional Parameters”

  1. JAX-RS simply isn’t meant for the type of flexibility you’re describing… I’d probably stick with a servlet in the case you describe. Also, if you need the kind of flexibility you’re describing from an “interface” standpoint, then HTTP headers and query parameters seem much more friendly than path params

  2. Ivo says:

    What about to use something like this:

    @GET
    @Produces(MediaType.TEXT_HTML)
    public String sayHtmlHello(@Context UriInfo info) {
    String identity = info.getQueryParameters().getFirst(“openid.identity”);
    String claimedid = info.getQueryParameters().getFirst(“openid.claimed_id”);

  3. hari says:

    how to handle an optional path param like below,

    1. /emp/12345/details

    2. /emp/details

    Here the emp_id can be optional and based on that it should be treated as 2 separate APIs. Any idea?

    • Michael says:

      You should use a @QueryParam instead of @PathParam for the details, then you do not have a conflict at all.
      /emp?details
      /emp/12345?details

      Another option, which only works for strictly formatted path parameters (such as numbers): Use regular expression in order to restrict the @PathParam. Only works if “details” does not match the regular expression.

  4. saravanan says:

    @GET
    @Path(“/queries/{UserId}/{Password}/{AmcId}{StartIndex : (/StartIndex)?}{FromDate : (/FromDate)?}”)

    not working
    its take a startindex as option all parameter

  5. Yes! Finally something about bouncy castle hire.

  6. Selma says:

    Hello, this weekend is nice for me, since this point in time i
    am reading this enormous educational article here at my residence.

  7. Roman says:

    First, optional parameters should be passed as query-parameter rather then path-parameters. Second, content-type negotiation should be done via the appropriate content-type header field of the HTTP request.

    Relating to question #3 about the differentiation between /emp/12345/details and /emp/details – JAX-RS allows for Regex – so while the first one could be @PathParam(“/emp/{id : \\d}/details”) while the latter one is simply @PathParam(“/emp/details”). This can be further extended to include negative lookaheads like this:
    @Path(“/emp/{foo : (?!noname)(?!invalidname)\\w}/bar”) – this will match URL /emp/name/bar but not /emp/noname/bar or /emp/invalidname/bar. This way you can have multiple resources within the same path-segment (f.e. /emp/…) and still being able to distinguish what method to invoke.

  8. Roman says:

    As I re-read on PathSegments, optional path-segments could also be achieved with PathSegments – f.e. a resource like:

    @Path(“/cars/{brand}”)
    public class CarResource {
    @GET
    @Path(“/{model : .+}/year/{year}”)
    @Produces(…)
    public Response getCarDetails(@PathParam(“brand”) String brand,
    @PathParam(“model”) List model,
    @PathParam(“year”) String year) {

    }
    }

    would service f.e. the following HTTP request: GET /cars/mercedes/c200/blueTEC/year/2014 where model contains c200 and blueTEC as PathSegments.
    }

    Also MatrixParam could be used to contain optional parameters:

    @Path(“/cars/{brand}”)
    public class CarResource {
    @GET
    @Path(“/{model : .+}/year/{year}”)
    @Produces(…)
    public Response getCarDetails(@PathParam(“brand”) String brand,
    @PathParam(“model”) String model,
    @MatrixParam(“color”)@DefaultValue(“black”) String color,
    @PathParam(“year”) String year) {

    }
    }

    would serve the following request: GET /cars/mercedes/c200;color=silver/year/2014

    Instead of ‘@PathParam(“model) String model, @MatrixParam(“color”) @DefaultValue(“black”) String color’ you could also declare ‘@PathParam(“model”) PathSegment model’ directly and then use model.getMatrixParameters().getFirst(“color”) to obtain the specified color

  9. Roman says:

    small fix for the above example as the board removed the generic type of the list – the list should hold PathSegment objects

  10. Magnificent beat ! I would like to apprentice wile you amend your website, how cann i subscribe
    for a blog website? The account helped me a acceptable deal.

    I had been tiny bit acquainted of this your broadcast offered bright clear idea

  11. Rob says:

    Avoid Glorifying Your Product ‘ Offer Solutions Instead.
    Remember that your health and future wellbeing is
    most important. * Will provide an initial consultation free-of-charge.

  12. Samantha says:

    At this time it sounds like WordPress is the preferred blogging platform available right
    now. (from what I’ve read) Is that what you are using on your blog?

  13. Great weblog right here! Additionally your website
    quite a bit up fast! What web host are you the use
    of? Can I am getting your associate hyperlink to your host?
    I desire my site loaded up as fast as yours lol

  14. Shakeel Shrestha says:

    Hello,

    In Flexible @Path Parameters in JAX-RS section example, if we pass multidigit value in locationId, we get only single digit value in response. How to overcome this problem?

  15. Ken Y-N says:

    Regarding multi-digit location IDs, I answered this on StackOverflow:

    @Path(“/location/{locationId}{path:(/[^/]+?)*}”)

  16. Ulrich says:

    It’s actually a great and helpful piece of info. I’m happy thaat you just shared thbis uzeful information with us.
    Please keep us informed like this. Thanks for sharing.

  17. Anonymous says:

    f

  18. Antonio says:

    Guys, any idea about how to match an optional path from a specific list of options?

  19. Traduttore says:

    Hi,
    I have this path rules:

    @GET
    @Path(“{format:([^/]+?)?}”)

    that it matchs with http://www.site.com/it or http://www.site.com , and that’s perfect for me. Now, I want to add a new rule that matchs with:
    http://www.site.com/it/search or http://www.site.com/search.
    I have tried this one:

    @GET
    @Path(“{format:([^/]+?)?}/search”)
    but actually the first rule got fired, how can I do to exclude from the first one some strings (like search and others one) or as opposite solutions, limit the optional path to some specific values, like it,en,fr, etc….
    Thank you

  20. Ravindra says:

    Can we delete any record through @FormParam ? there is any option to delete record without using the @PathParam and QueryParam in Restful Api?

  21. […] se escribe el código, ¿qué has intentado hasta ahora? Estoy utilizando JAX-RS. Me refería a nakov.com/blog/2009/07/15/…. Es necesario usar regex, o hay alguna otra manera? ¿Cómo podría distinguir optional1 y […]

  22. […] ce que vous avez essayé jusqu'à présent? Je suis à l'aide de JAX-RS. J'ai fait référence à nakov.com/blog/2009/07/15/…. Est-il nécessaire d'utiliser des regex, ou est-il un autre moyen? Comment voulez-vous de les […]

RSS feed for comments on this post. TrackBack URL

LEAVE A COMMENT