Spring 5 has also been out for a long time, there are some new things we need to explore slowly. Recently, when I was looking at the SpringMVC source code, I saw the following piece of code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
    * Initialize the path to use for request mapping.
    * <p>When parsed patterns are {@link #usesPathPatterns() enabled} a parsed
    * {@code RequestPath} is expected to have been
    * {@link ServletRequestPathUtils#parseAndCache(HttpServletRequest) parsed}
    * externally by the {@link org.springframework.web.servlet.DispatcherServlet}
    * or {@link org.springframework.web.filter.ServletRequestPathFilter}.
    * <p>Otherwise for String pattern matching via {@code PathMatcher} the
    * path is {@link UrlPathHelper#resolveAndCacheLookupPath resolved} by this
    * method.
    * @since 5.3
    */
protected String initLookupPath(HttpServletRequest request) {
    if (usesPathPatterns()) {
        request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
        RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
        String lookupPath = requestPath.pathWithinApplication().value();
        return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
    }
    else {
        return getUrlPathHelper().resolveAndCacheLookupPath(request);
    }
}

This code initializes the path used for RequestMapping with a string pattern match performed by PathMatcher.

This is a method in the AbstractHandlerMapping abstract class, which plays a key role in Spring WebFlux, for URL matching. I was curious: wasn’t this always the job of AntPathMatcher? I’ve used it before when I was learning Spring Security.

This method is new in Spring5, there was no such method before. In the old SpringMVC, when we need to get the current request path, we directly get it by the following way.

1
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);

But now it has changed and now gets the current request path in the following way.

1
String lookupPath = initLookupPath(request);

The main difference between the two ways is that the initLookupPath method has an additional usesPathPatterns option, which is new in Spring5 and is especially important if you use WebFlux in your project! Because WebFlux only has the PathPattern pattern, not the AntPathMatcher.

The software versions used in this article are as follows.

  • SpringBoot: 2.4.2
  • JDK: 8
  • Spring Framework: 5.3.19

AntPathMatcher

When we use the @RequestMapping annotation to mark the request interface (or use its similar methods like @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping), we can use some wildcard characters to match URL address, for a simple example, suppose I have the following five handler methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class AntPathMatcherDemo {

    @GetMapping("/hello/**/hello")
    public String hello() {
        return "/hello/**/hello";
    }
    @GetMapping("/h?llo")
    public String hello2() {
        return "/h?llo";
    }
    @GetMapping("/**/*.html")
    public String hello3() {
        return "/**/*.html";
    }
    @GetMapping("/hello/{p1}/{p2}")
    public String hello4(@PathVariable String p1, @PathVariable String p2) {
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
        return "/hello/{p1}/{p2}";
    }
    @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
    public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
        System.out.println("name = " + name);
        System.out.println("version = " + version);
        System.out.println("ext = " + ext);
    }

}

The meanings of several wildcard characters are as follows.

Wildcard Meaning
** Match 0 or more directories
* Match 0 or more characters
? Match any single character

Now that we know what wildcards mean, let’s talk about what requests each handler method can match.

  • The first method, can take requests like /hello/123/123/hello, /hello/a/hello, and /hello/hello, because the ** in between represents 0 or more directories.
  • The second method, which can receive requests such as /hallo, /hello, /hMllo, note that it cannot receive /haallo or /hllo, because ? represents a character.
  • The third method can receive any request with a .html suffix, such as /aaa/bb/cc.html, /aa.html or /aa/aa.html.
  • The fourth method, which we are all familiar with, is used by everyone in RESTful-style api design, and receives requests in a format similar to /hello/aa/bb, where parameter p1 corresponds to aa and parameter p2 corresponds to bb.
  • The fifth method uses a regular pattern, with the name, version and ext parameters expressed as a regular pattern, and it can receive requests such as /spring-web-3.0.5.jar, where the final parameter name is spring-web, version is 3.0.5 and ext is . jar.

This is a feature that existed in SpringMVC before, whether you used it or not, it exists consistently anyway.

So who supports this feature? It’s the AntPathMatcher.

AntPathMatcher is a path matcher that implements the Ant style.

The Ant-style path rules are actually the same three path matching strings we introduced in the code above, which is very simple. This path matching rule comes from the Apache Ant project, which is rarely used anymore, and its replacement is the well-known Maven.

If you are lucky enough to maintain some old projects from before 2010, you may come across Ant.

AntPathMatcher is actually very widely used in SpringMVC, not only in @RequestMapping, but also in other places where path matching is involved, for example, when we configure static resource filtering in the SpringMVC configuration file, it is also Ant style path matching.

1
<mvc:resources mapping="/**" location="/"/>

Ant-style path matchers are also used for interceptor path registration, cross-domain path matching, and so on.

Overall, AntPathMatcher is a relatively primitive path matching solution in Spring, which is simple but inefficient and inconvenient when dealing with URL encoding.

As a result, Spring5 has introduced PathPattern.

PathPattern

PathPattern is designed for web applications and is mostly similar to the previous AntPathMatcher, but there are some minor differences

If it is a Servlet application, the official recommended URL matching solution is PathPattern (of course you can also choose the older AntPathMatcher), although the official recommendation is PathPattern, the default is still AntPathMatcher; if you are using WebFlux, PathPattern is the only solution.

Note that PathPattern is a very new thing. Before Spring 5.3, we were limited to AntPathMatcher in our Servlet applications, and since Spring 5.3, we can use PathPattern.

PathPattern pre-parses URL rules into PathContainer, which is much faster for URL path matching, and the difference between PathPattern and AntPathMatcher is mainly in two aspects.

  1. PathPattern only supports the use of ** at the end, if you use ** in the middle of the path, it will throw an exception. The first and third methods above will throw exceptions in PathPattern mode.

  2. PathPattern supports path matching using a method such as {*path}. This way of writing can also match to multi-level paths and assign the matched values to path variables.

    Take the following path as an example.

    1
    2
    3
    4
    5
    6
    7
    8
    
    @RestController
    public class PathPatternDemo {
    
        @GetMapping("/path/{*path}")
        public void hello6() {
            System.out.println("test");
        }
    }
    

    If the request path is http://localhost:8080/path/aa, then the value of the path parameter is /aa.

    If the request path is http://localhost:8080/path/aa/bb/cc/dd, then the value of the path parameter is /aa/bb/cc/dd.

    This is also relatively new, as it was not in the previous AntPathMatcher.

We know that /** and /{*pathVariable} both have the “ability” to match all remaining paths, but what is the difference between them?

  1. /** can match successfully, but can’t get the value of the dynamically successful matched element
  2. /{*pathVariable} can be considered as an enhanced version of /**: it can get the value of this part of the dynamically successful match

Since both /** and /{*pathVariable} have the ability to match the remaining paths, what is the priority relationship between them if they are put together?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
public void test2() {
    PathPattern pattern1 = PathPatternParser.defaultInstance.parse("/api/yourbatman/{*pathVariable}");
    PathPattern pattern2 = PathPatternParser.defaultInstance.parse("/api/yourbatman/**");

    SortedSet<PathPattern> sortedSet = new TreeSet<>();
    sortedSet.add(pattern1);
    sortedSet.add(pattern2);

    System.out.println(sortedSet);
}

The output is as follows.

1
[/api/yourbatman/**, /api/yourbatman/{*pathVariable}]

When testing, I put /{*pathVariable} into set first and /** later, but in the end, /** comes first.

Conclusion: When both appear at the same time (there is a conflict), /** matches first.

How to use PathPattern in your application

By default, AntPathMatcher is still used in SpringMVC, so how to enable PathPattern? It’s easy, just add the following configuration to your SpringBoot project.

1
2
3
4
5
6
7
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPatternParser(new PathPatternParser());
    }
}

After adding this configuration, in the code posted at the beginning of our article, it goes to the if branch, which in turn uses PathPattern to parse the request URL.

Starting with Spring Boot 2.6 the default strategy for matching request paths to Spring MVC processing maps has been changed from AntPathMatcher to PathPatternParser. you can set spring.mvc.pathmatch.matching-strategy to ant-path-matcher to change it.

1
2
# path-pattern-parser (default)
spring.mvc.pathmatch.matching-strategy=ant-path-matcher

PathPattern removes the Ant character, but maintains good backward compatibility: except for not supporting writing ** in the middle of the path, all other matching rules remain the same behavior as AntPathMatcher, and also adds powerful support for {*pathVariable}.

PathPattern syntax is more suitable for web applications, non-Web environments still have one and only one option, which is AntPathMatcher, because PathPattern is designed for Web environments and cannot be used in non-Web environments. So path matching scenarios like resource loading, package scanning, etc. are still left to AntPathMatcher to complete.

Reference http://www.enmalvi.com/2022/09/06/springmvc-pathpattern/