Spring @PathVariable Head-slapper

Recently some peers and I spent a little time spinning around a goofy little annotation trick that Spring uses, that bit us because of the way p-code is generated. It all makes sense afterwards, but at the time it was a little frustrating and puzzling.

Taken straight from the Spring documentation, the following example shows a similar use to what we’d done.

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
  Owner owner = ownerService.findOwner(ownerId);
  model.addAttribute("owner", owner);
  return "displayOwner";
}

Our code looked pretty much the same, with our names and useful bits, of course. Quickly scanning that documentation shows that our syntax was accurate. The compiler didn’t complain, and sometimes it worked. Where it got confusing is that the code would work just fine when run in an integration test using HTTPUnit (in the IDE and run by Ant) and in a Servlet engine (Tomcat, specifically) when launched from Eclipse. Some people had success when deploying to Tomcat using an Eclipse-created WAR file, but some people experienced failure. Everyone failed when using the Ant-built WAR file.

When the error occurred, the root cause of the Exception caught ultimately was the following:

java.lang.IllegalStateException: No parameter name specified for argument of type [java.lang.String], and no parameter name information found in class file either.

When you look at the code, you can see the @RequestMapping has the appropriate {variable} notation, and that the parameter list has a variable of the same name, of type String, as expected (other types can be used, but ours was a String also).

A peek at the Ant script gave a clue to the solution. Changing the javac target’s debug attribute to “on” allowed the Ant-built WAR file to also deploy and run with success. That’s when the head-slapping began.

When the code is compiled with debug, as it is when working in the IDE, and apparently is sometimes when exporting from the IDE (probably some of us have a workspace setting different than the others), the name of the parmeter is available to the JVM at runtime. When the code is compiled without debugging, as the Ant script was doing, then the parameter name is lost, truncated by the p-code generator based on its type and order and other factors.

Adding the name to the @PathVariable annotation allows the runtime to find the correct parameter even without debug information in the class file. Again, straight from the same documentation, just a couple paragraphs down from the other example shows the more correct way to declare the @PathVariable. Right above the example on their page is a discrete mention of this fact, and a recommendation that you specify the name. Below is the subtle difference in the declaration, one that makes all the difference.

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String ownerId, Model model) {
  // implementation omitted
}

While it’s convenient to have Spring work this out for you during the development cycle, it seems more appropriate that the value be required which can be achieved simply by removing the default from the annotation. Since it isn’t that way, it’s certainly a good practice to get into to always provide the name (or names) of your path variable when annotating your controllers in Spring.

One thought on “Spring @PathVariable Head-slapper

  1. Andy says:

    Good post. Spring 3.0’s docs are good but definitely lacking some details. The @PathVariable is a little tricky especially when either a request parameter isnt found or when there is a type mismatch. Ive just been using plain old: String access_token = req.getParameter(“access_token”);

  2. Jeff Warren says:

    I think you’re mixing your annotations. @RequestParameter would match what you noted. @PathVariable pulls parts out of the URL before the query string would begin. In the example, a URL of /owners/something would put the value of “something” in the variable ownerId.

    Unlike @RequestParameter, @PathVariable is always required, or rather, when missing (as in /owners from the example), the @RequestMapping won’t be matched.

    You can add the “required” parameter to the @RequestParameter to control whether to allow it to be blank. If you mark it as required and it is missing that method won’t match the @RequestMapping.

    Note, though, that @RequestParameter will suffer the same head-slap as the parameter name is lost without debugging symbols. It can be solved with the same name parameter.

  3. Andy says:

    Ahh ok thanks for clarifying. Makes sense.

  4. Asif says:

    You saved me from wasting time on this head slapper. After deploying my application in staging, I came across this error. Luckily, I decided to Google it before wasting any time as it didn’t make any sense!

    I am also using Eclipse and running tomcat inside it.

  5. Markus says:

    Even almost half a decade later this blog entry:

    a) is valid
    b) saved my day
    c) makes me laugh

    Thanks for sharing and kind regards,

    Markus

  6. Josh says:

    Thanks so much for walking through this! Can’t tell you how much it helped.

  7. mahen says:

    Thank you for the post. This saved my lot of time. Very good observation.

  8. m0j0hn says:

    In the interest of leaving deep tracks –
    for myself and others –
    since this same problem has been biting me for a full day now, til I found this post:

    When seeing this error message:
    java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.

    Change this:
    someFunction(@RequestParam String stringname)
    to this:
    someFunction(@RequestParam(“stringName”) String stringname)

    where “someFunction()” and “stringname” are the names you are using.
    And notice that “stringName” can be different name from “stringname”,
    which is why I capitalized them differently.

  9. Krish says:

    Good one. Thanks.

  10. Bharath says:

    Really useful to resolve my blocker issue

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*