How to

Spring Boot: Using the new filter for trailing slash handling

Post - Spring Boot: Using the new filter for trailing slash handling
June 13, 2024 48 minutes ago Tags 4 min read Comments
Share to Linkedin
Share to X (Twitter)
Share to Reddit
Share to Bluesky
Copy link

Table of contents

Open Table of contents

Introduction

I’ve been migrating the HawAPI - A Free and Open Source API for Stranger Thingsproject from Spring Boot 2.7 to 3.3 and one intresting thing that I notice while running the tests is: Tests where the URL ends with a trailing slash (/) started failing with 404 status code.

Before Spring Boot 3.X (Spring Framework 6.X) the value of setUseTrailingSlashMatch was always true. However, with the latest major upgrade, this parameter has been deprecated and its value is now fixed at false in all locations. (See commit b312eca)

According to the docs:

[…] support for trailing slashes is deprecated as of 6.0 in favor of configuring explicit redirects through a proxy, Servlet/web filter, or a controller. - PathPatternParser.java#L45-L61

Solutions for handle trailing slash

With that being said, let’s dive into our options:

Declaring multiple routes for every handler

The first solution is to explicit declare a second route mapping in all route handlers:

UserController.java
1
@GetMapping({"/users/{uuid}", "/users/{uuid}/"})
2
public ResponseEntity<UserDTO> findUser(@PathVariable UUID uuid) {
3
// ...
4
}
5
6
@PostMapping({"/users", "/users/"})
7
public ResponseEntity<UserDTO> registerUser(UserRegistrationDTO user) {
8
// ...
9
}

The problem is: this solution will only work effectively if the application has a few number of mappings.

Using a custom OncePerRequestFilter

To avoid adding a second route mapping to all routes, we can create a custom OncePerRequestFilter to redirect all request urls that contain trailing slash (/) to the url without it and using the 301 (Moved Permanently) status code.

TrailingSlashHandlerFilter.java
1
@Component
2
public class TrailingSlashHandlerFilter extends OncePerRequestFilter {
3
4
@Overastro-tDfHKSGh
5
protected void doFilterInternal(
6
HttpServletRequest request,
7
HttpServletResponse response,
8
FilterChain filterChain
9
) throws ServletException, IOException {
10
String requestUri = request.getRequestURI();
11
12
if (requestUri.endsWith("/")) {
13
String newUrl = requestUri.substring(0, requestUri.length() - 1);
14
response.setStatus(HttpStatus.MOVED_PERMANENTLY.value());
15
response.setHeader(HttpHeaders.LOCATION, newUrl);
16
return;
17
}
18
19
filterChain.doFilter(request, response);
20
}
21
}

It’s a good option but we’ll need to redirect all requests the url without trailing slash, or vice versa.

Using the new UrlHandlerFilter

At the moment of writing this post, the version 6.2.0 wasn’t released yet. To be able to use the new UrlHandlerFilter i’m using the version 6.2.0-M4. See how to install:

Overriding Spring Framework
  1. Add the milestone repository to your pom.xml file:
pom.xml
<repositories>
<repository>
<id>maven_central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
  1. Overastro-tDfHKSGh spring framework version:
pom.xml
1
<properties>
2
<java.version>21</java.version>
3
<spring-framework.version>6.2.0-M4</spring-framework.version>
4
</properties>

In this version we can use the new UrlHandlerFilter with a few possible options:

  1. Redirect all or only specific urls with 301 (Moved Permanently)
  2. Handle all or only specific urls (Same as before Spring Boot 3.X (Spring Framework 6.X))
TrailingSlashHandlerFilter.java
1
@Component
2
public class TrailingSlashHandlerFilter extends OncePerRequestFilter {
3
4
@Overastro-tDfHKSGh
5
protected void doFilterInternal(
6
HttpServletRequest request,
7
HttpServletResponse response,
8
FilterChain filterChain
9
) throws ServletException, IOException {
10
// Redirecting
11
UrlHandlerFilter filter = UrlHandlerFilter.trimTrailingSlash("/**").andRedirect(HttpStatus.PERMANENT_REDIRECT).build();
12
13
// Or transparently handle those for HTTP clients, without any redirect:
14
UrlHandlerFilter filter = UrlHandlerFilter.trimTrailingSlash("/**").andHandleRequest().build();
15
filter.doFilter(request, response, filterChain);
16
}
17
}

The catch-all pattern (/**) matches any path, including nested paths.

- /api/test will - be handled.
- /api/test/subpath - will also be handled.

Conclusion

In this article, we discussed the deprecation of the trailing slash matching configuration option in Spring Boot 3 showing three possible ways, including the official UrlHandlerFilter filter, to fix this small but significant change.

According to @rstoyanchev, this deprecation will not be reverted:

[…] However, undeprecating trailing slash matching at this point, only to deprecate that later again, will only add to the confusion. - issuecomment-1748751881

Resources and References

Enjoy this post? Feel free to share!
Share to Linkedin
Share to X (Twitter)
Share to Reddit
Share to Bluesky
Copy link

Comments

© 2023 Lucas Josino All Rights Reserved.