From 8bb63081a87cadb7fbf8ba306187564b9d2f0b1c Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 5 Nov 2025 14:58:58 +0100 Subject: [PATCH] Document PathPattern matching for single/multiple segments This commit improves the reference document to better reflect the different between `*` or `{name}` on one side, and `**` or `{*path}` on the other. The former patterns only consider a single path segment and its content, while the latter variants consider zero or more path segments. This explains why `/test/{*path}` can match `/test`. Closes gh-35727 --- .../controller/ann-requestmapping.adoc | 55 +----------------- .../mvc-controller/ann-requestmapping.adoc | 56 +----------------- .../ROOT/partials/web/uri-patterns.adoc | 57 +++++++++++++++++++ .../web/util/pattern/PathPatternTests.java | 4 +- 4 files changed, 61 insertions(+), 111 deletions(-) create mode 100644 framework-docs/modules/ROOT/partials/web/uri-patterns.adoc diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc index 4cd8a601bf0..49bbd3b6a78 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc @@ -89,60 +89,7 @@ Kotlin:: You can map requests by using glob patterns and wildcards: -[cols="2,3,5"] -|=== -|Pattern |Description |Example - -| `spring` -| Literal pattern -| `+"/spring"+` matches `+"/spring"+` - -| `+?+` -| Matches one character -| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+` - -| `+*+` -| Matches zero or more characters within a path segment -| `+"/resources/*.png"+` matches `+"/resources/file.png"+` - -`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+` - -| `+**+` -| Matches zero or more path segments -| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+` - -`+"/**/resources"+` matches `+"/spring/resources"+` and `+"/spring/framework/resources"+` - -`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path. - -`+"/**/{name}/resources"+` is invalid as only a literal pattern is allowed right after `+**+`. -`+"/**/project/{project}/resources"+` is allowed. - -`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. - -| `+{name}+` -| Matches a path segment and captures it as a variable named "name" -| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+` -`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment. - -| `{name:[a-z]+}` -| Matches the regexp `[a-z]+` as a path variable named "name" -| `/projects/{project:[a-z]+}/versions` matches `/projects/spring/versions` but not `/projects/spring1/versions` - -| `+{*path}+` -| Matches zero or more path segments and captures it as a variable named "path" -| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+` - -`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+` - -`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path. - -`+"/{*path}/{name}/resources"+` is invalid as only a literal pattern is allowed right after `{*path}`. -`+"/{*path}/project/{project}/resources"+` is allowed. - -`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. - -|=== +include::partial$web/uri-patterns.adoc[leveloffset=+1] Captured URI variables can be accessed with `@PathVariable`, as the following example shows: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc index 534cc947a10..ad90d29364a 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc @@ -105,61 +105,7 @@ customizations of path matching options. You can map requests by using glob patterns and wildcards: -[cols="2,3,5"] -|=== -|Pattern |Description |Example - -| `spring` -| Literal pattern -| `+"/spring"+` matches `+"/spring"+` - -| `+?+` -| Matches one character -| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+` - -| `+*+` -| Matches zero or more characters within a path segment -| `+"/resources/*.png"+` matches `+"/resources/file.png"+` - -`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+` - -| `+**+` -| Matches zero or more path segments -| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+` - -`+"/**/resources"+` matches `+"/spring/resources"+` and `+"/spring/framework/resources"+` - -`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path. - -`+"/**/{name}/resources"+` is invalid as only a literal pattern is allowed right after `+**+`. -`+"/**/project/{project}/resources"+` is allowed. - -`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. - -| `+{name}+` -| Matches a path segment and captures it as a variable named "name" -| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+` - -`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment. - -| `{name:[a-z]+}` -| Matches the regexp `"[a-z]+"` as a path variable named "name" -| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"` - -| `+{*path}+` -| Matches zero or more path segments and captures it as a variable named "path" -| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+` - -`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+` - -`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path. - -`+"/{*path}/{name}/resources"+` is invalid as only a literal pattern is allowed right after `{*path}`. -`+"/{*path}/project/{project}/resources"+` is allowed. - -`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. - -|=== +include::partial$web/uri-patterns.adoc[leveloffset=+1] Captured URI variables can be accessed with `@PathVariable`. For example: diff --git a/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc new file mode 100644 index 00000000000..f0ff6177a8d --- /dev/null +++ b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc @@ -0,0 +1,57 @@ +[cols="2,3,5"] +|=== +|Pattern |Description |Example + +| `spring` +| Literal pattern +| `+"/spring"+` matches `+"/spring"+` + +| `+?+` +| Matches one character +| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+` + +| `+*+` +| Matches zero or more characters within a path segment +| `+"/resources/*.png"+` matches `+"/resources/file.png"+` + +`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`. + +`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present. + +| `+**+` +| Matches zero or more path segments +| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+` + +`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+` + +`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path. + +`+"/**/{name}/resources"+` is invalid as only a literal pattern is allowed right after `+**+`. +`+"/**/project/{project}/resources"+` is allowed. + +`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. + +| `+{name}+` +| Similar to `+*+`, but also captures the path segment as a variable named "name" +| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+` + +`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment. + +| `{name:[a-z]+}` +| Matches the regexp `"[a-z]+"` as a path variable named "name" +| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"` + +| `+{*path}+` +| Similar to `+**+`, but also captures the path segments as a variable named "path" +| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+` + +`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+` + +`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path. + +`+"/{*path}/{name}/resources"+` is invalid as only a literal pattern is allowed right after `{*path}`. +`+"/{*path}/project/{project}/resources"+` is allowed. + +`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. + +|=== \ No newline at end of file diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java index 7f9df03242f..f0f215daaa9 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java @@ -149,14 +149,14 @@ class PathPatternTests { checkMatches("/*/bar", "/foo/bar"); checkNoMatch("/*/bar", "/foo/baz"); checkNoMatch("/*/bar", "//bar"); + checkNoMatch("/*/bar", "/bar"); checkMatches("/f*/bar", "/foo/bar"); - checkMatches("/*/bar", "/foo/bar"); checkMatches("a/*","a/"); checkMatches("/*","/"); checkMatches("/*/bar", "/foo/bar"); checkNoMatch("/*/bar", "/foo/baz"); + checkNoMatch("/*/bar", "/bar"); checkMatches("/f*/bar", "/foo/bar"); - checkMatches("/*/bar", "/foo/bar"); checkMatches("/a*b*c*d/bar", "/abcd/bar"); checkMatches("*a*", "testa"); checkMatches("a/*", "a/");