Close

Spring MVC - URI Patterns

[Updated: Mar 29, 2018, Created: Mar 26, 2018]

In this tutorial we will learn how many different kind of URI patterns can be used with @RequestMapping#value.

Spring uses AntPathMatcher for pattern matching. Following rules are applied:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more directories in a path

Other than above, regex pattern can also be used with template variables.

Let's go through examples to understand how these rules work.

Examples

Pattern with ?

Each ? matches a single character:

@Controller
@ResponseBody
public class MyController {

  @RequestMapping("/car?/s?o?/info")
  public String test1(HttpServletRequest request) {
      return "from test1(), request uri: " + request.getRequestURI();
  }
    .............
}

Above handler will map to cars/shop/info, cart/show/info etc:

$ curl http://localhost:8080/cars/shop/info
from test1(), request uri: /cars/shop/info
$ curl http://localhost:8080/cart/show/info
from test1(), request uri: /cart/show/info

Pattern with *

Each * matches zero or more characters but within a single path segment (path segments are separated by /):

@Controller
@ResponseBody
public class MyController {
    .............
  @RequestMapping("/c*/s*d/info")
  public String test2(HttpServletRequest request) {
      return "from test2(), request uri: " + request.getRequestURI();
  }
    .............
}

Above handler will map to cars/speed/info, cabbie/signalized/info etc:

$ curl http://localhost:8080/cars/speed/info
from test2(), request uri: /cars/speed/info
$ curl http://localhost:8080/cabbie/signalized/info
from test2(), request uri: /cabbie/signalized/info

Pattern with **

Double wildcards can match anything including forward slashes. They are used to match directories in path.

@Controller
@ResponseBody
public class MyController {
    .............
  @RequestMapping("/card/**")
  public String test3(HttpServletRequest request) {
      return "from test3(), request uri: " + request.getRequestURI();
  }
    .............
}

Above handler will map to /card, /card/about, /card/visa/registration etc:

$ curl http://localhost:8080/card
from test3(), request uri: /card
$ curl http://localhost:8080/card/about
from test3(), request uri: /card/about
$ curl http://localhost:8080/card/visa/registration
from test3(), request uri: /card/visa/registration

Path variables

Spring supports URI template capturing via @PathVariable annotation. A template can have a regex pattern as well.

@Controller
@ResponseBody
public class MyController {
    .............
  @RequestMapping("/card/{type}/{id:i.*}")
  public String test4(@PathVariable String type, @PathVariable String id,
                      HttpServletRequest request) {
      return "from test4(), request uri: " + request.getRequestURI() + "\n" +
              "type: " + type + ", id: " + id;
  }
    .............
}
$ curl http://localhost:8080/card/visa/i2345d
from test4(), request uri: /card/visa/i2345d
type: visa, id: i2345d
$ curl http://localhost:8080/card/master/i3234
from test4(), request uri: /card/master/i3234
type: master, id: i3234

Selection between multiple matches

When multiple patterns match a request URI, they must be compared to find the best match. This done by finding the most specific match. A score is calculated to find the most specific match. The lowest score wins the comparison. Following rules are applied:

  • A single wildcard (*) is counted as one.
  • A double wildcard (**) is counted as two.
  • URI path without any pattern (e.g. /cars/dealer) has zero count. Also a single '?' is counted as zero (e.g. /car?/d??r has total zero count).
  • If one of the handlers has just "/**" (i.e. @RequestMapping("/**")) then it is always matched at end. Also patterns like /employee/** (prefix pattern, the one ending with /**) are matched after the patterns which don't have double wildcards.
  • One template variable within a pair of curly braces (e.g: /card/{type}) is counted as one.
  • Regex with * inside template variable (e.g. /card/{id:n.*} is also counted as one. Whereas, other regex quantifiers (+ or ?) are not counted at all.
  • If multiple patterns have equal score, the longer pattern is chosen. Here length is calculated via String#length(). In case of Template variables, content between { and } are reduced to a single # (e.g. /card/{var1}/{var2} to /card/#/#) before calculating length.
  • At this point, if two patterns have same length, then the pattern with less number of wildcards (*) is selected.
  • At this point, if two patterns have same number of wildcards(*), then the pattern with less number of template variables are selected.

Above logic is implemented in AntPathStringMatcher#compare method (this class is nested class of AntPathMatcher).

Let's see more examples:

@Controller
@ResponseBody
public class MyController {

  @RequestMapping("/car?/s?o?/info")//score 0
  public String test1(HttpServletRequest request) {
      return "from test1(), request uri: " + request.getRequestURI();
  }

  @RequestMapping("/c*/s*d/info")//score 2, length = 12
  public String test2(HttpServletRequest request) {
      return "from test2(), request uri: " + request.getRequestURI();
  }

  @RequestMapping("/card/**")//score 2 but will be used after others because of prefix pattern
  public String test3(HttpServletRequest request) {
      return "from test3(), request uri: " + request.getRequestURI();
  }

  @RequestMapping("/card/{type}/{id:i.*}")//2 template variables + 1 wildcard = score 3
  public String test4(@PathVariable String type, @PathVariable String id,
                      HttpServletRequest request) {
      return "from test4(), request uri: " + request.getRequestURI() + "\n" +
              "type: " + type + ", id: " + id;
  }

  @RequestMapping("/card/{type}/{id:i.+}")//score 2, length 9 (/card/#/#)
  public String test5(@PathVariable String type, @PathVariable String id,
                      HttpServletRequest request) {
      return "from test5(), request uri: " + request.getRequestURI() + "\n" +
              "type: " + type + ", id: " + id;
  }
}

Request URI: 'card/shod/info':

This URI matches all handlers methods, but test1() will be selected because it has the lowest score:

$ curl http://localhost:8080/care/shod/info
from test1(), request uri: /care/shod/info

Request URI 'card/send/info':

It matches all handlers except for test1().

test2() and test5() have the lowest count (2), but test2() will be selected because it is longer in length.

$ curl http://localhost:8080/card/send/info
from test2(), request uri: /card/send/info

Let's see other controller:

@Controller
@RequestMapping("/other/")
@ResponseBody
public class MyController2 {

  @RequestMapping("/c*/s*d/info")//score 2, length = 12, wildcards=2
  public String otherTest1(HttpServletRequest request) {
      return "from otherTest1(), request uri: " + request.getRequestURI();
  }

  @RequestMapping("/card/{type}/info")//score 1, length 12 (/card/#/info), wildcards=0
  public String otherTest2(@PathVariable String type, HttpServletRequest request) {
      return "from otherTest2(), request uri: " + request.getRequestURI() + "\n" +
              "type: " + type;
  }
}

In this case the request uri /other/card/send/info matches both methods but the second method will be selected because it has the lowest score:

$ curl http://localhost:8080/other/card/send/info
from otherTest2(), request uri: /other/card/send/info
type: send

Here is another controller:

@Controller
@RequestMapping("/another/")
@ResponseBody
public class MyController3 {


  @RequestMapping("/c*/s*d/info")//score 2, length = 12, wildcards=2
  public String anotherTest1(HttpServletRequest request) {
      return "from anotherTest1(), request uri: " + request.getRequestURI();
  }


  @RequestMapping("/card/{type}/inf{id:.+}")//score 2, length 12 (/card/#/info), wildcard = 0
  public String anotherTest2(@PathVariable String type, @PathVariable String id,
                      HttpServletRequest request) {
      return "from anotherTest2(), request uri: " + request.getRequestURI()+"\n"+
              "type: "+type+", id: "+id;
  }
}

In this case the request uri /another/card/send/info matches both methods. Both methods have same scores and same length but last method will be selected because it has the less number of wildcards (*):

$ curl http://localhost:8080/another/card/send/info
from anotherTest2(), request uri: /another/card/send/info
type: send, id: o

Also check out following tutorials, where URI path pattern is involved:

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 5.0.4.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.0.1 Java Servlet API
  • jstl 1.2 javax.servlet:jstl
  • JDK 1.8
  • Maven 3.3.9

Spring Mvc Uri Pattern Select All Download
  • spring-uri-pattern-example
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also