eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

In this tutorial, we’ll explore the changes in URL-matching introduced by Spring Boot 3 (Spring 6). Spring framework 6.0 deprecated transparent support for trailing slashes in favor of configuring explicit redirects through a proxy, Servlet/web filter, or a controller.

URL matching is a powerful feature in Spring Boot that enables developers to map specific URLs to controllers and actions in a web application. This feature enables the easy organization and navigation of the application, leading to a better user experience.

To handle URL mapping, Spring Boot uses a powerful mechanism called the DispatcherServlet, which acts as the front controller for the application. The servlet forwards requests to the appropriate controller based on the URL. The DispatcherServlet uses a set of rules, known as mappings, to determine which controller should handle a given request.

We can start by reading more details about an older version of URL matching in Spring Boot 2 (Spring 5).

2. Spring MVC and Webflux URL Matching Changes

Spring Boot 3 significantly changed the trailing slash matching configuration option. This option determines whether or not to treat a URL with a trailing slash the same as a URL without one. Previous versions of Spring Boot set this option to true by default. This meant that a controller would match both “GET /some/greeting” and “GET /some/greeting/” by default:

@RestController
public class GreetingsController {

    @GetMapping("/some/greeting")
    public String greeting {
        return "Hello";
    } 

}

As a result, if we try to access a URL with a trailing slash, we will receive a 404 error unless the controller is specifically set up to handle URLs with a trailing slash. This can lead to confusion and broken links if not properly handled.

Let’s explore some options for adapting to this change.

3. Configure the UrlHandlerFilter for Trailing Slash Match

Spring Framework 6.2 introduces the new UrlHandlerFilter to help redirect or rewrite incoming URLs with a trailing slash. Accordingly, it redirects/wraps a request for “/some/greeting/” to “/some/greeting”. The UrlHandlerFilter can be instantiated using one of two methods.

The first method is that when we request “/greetings/some-greeting/” it can respond with an HTTP 308 redirect status, and send the browser to the non-trailing slash URL “/greetings/some-greeting”.

UrlHandlerFilter urlHandlerFilter = 
  UrlHandlerFilter.trailingSlashHandler("/greetings/**").
  redirect(HttpStatus.PERMANENT_REDIRECT).
  build();

The second method is that when we request “/greetings/some-greeting/” it can wrap the request to act as if it was sent to “/greetings/some-greeting” and process the request.

UrlHandlerFilter urlHandlerFilter = 
  UrlHandlerFilter.trailingSlashHandler("/greetings/**").
  wrapRequest()
  .build();

The UrlHandlerFilter can be used with the Spring MVC Servlet API, and the Spring WebFlux Reactive API. We add a Filter by using a Spring bean, for which we provide a @Bean definition in the configuration:

@Bean
    public FilterRegistrationBean urlHandlerFilterRegistrationBean() {
        FilterRegistrationBean<OncePerRequestFilter> registrationBean = new FilterRegistrationBean<>();
        UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
          .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
	  .trailingSlashHandler("/greetings/**").wrapRequest()
          .build();
        registrationBean.setFilter(urlHandlerFilter);

        return registrationBean;
    }

We configured two request paths in the example Filter bean. The Servlet container automatically registers Filter beans.

4. Additional Route

Let’s update our existing applications to ensure URLs are handled correctly to accommodate this change. This can be done by adding specific URL mappings for each controller that handles URLs with a trailing slash.

For example, in our previous controller that handles the URL “/some/greeting“, we need to add a separate mapping for “/some/greeting/“. This will ensure that users can access the desired page even if they include a trailing slash in the URL.

@RestController
public class GreetingsController {

    @GetMapping("/some/greeting")
    public String greeting {
        return "Hello";
    } 

    @GetMapping("/some/greeting/")
    public String greeting {
        return "Hello";
    } 

}

Here is a reactive @RestController using Webflux:

@RestController
public class GreetingsControllerReactive {

    @GetMapping("/some/reactive/greeting")
    public Mono<String> greeting() {
        return Mono.just("Hello reactive");
    }

    @GetMapping("/some/reactive/greeting/")
    public Mono<String> greetingTrailingSlash() {
        return Mono.just("Hello with slash reactive");
    }
}

5. Configure Redirect Using a Custom Filter

When a request comes in, the Spring Boot filter chain determines which filters to apply based on the request and the registered filters. If the request is received by a traditional blocking I/O endpoint, such as a @RestController or a @Controller, the filter chain will apply any filters that implement the Filter interface. These filters block I/O and may interact with the Servlet API to read or modify the request or response.

To configure a URL redirect using a custom filter for blocking requests, we can follow these steps:

Firstly, we need to create a new class that implements the jakarta.servlet.Filter interface:

public class TrailingSlashRedirectFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI();

        if (path.endsWith("/")) {
            String newPath = path.substring(0, path.length() - 1);
            HttpServletRequest newRequest = new CustomHttpServletRequestWrapper(httpRequest, newPath);
            chain.doFilter(newRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }

    private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final String newPath;

        public CustomHttpServletRequestWrapper(HttpServletRequest request, String newPath) {
            super(request);
            this.newPath = newPath;
        }

        @Override
        public String getRequestURI() {
            return newPath;
        }

        @Override
        public StringBuffer getRequestURL() {
            StringBuffer url = new StringBuffer();
            url.append(getScheme()).append("://").append(getServerName()).append(":").append(getServerPort())
              .append(newPath);
            return url;
        }
    }
}

We implement the Filter interface in this custom filter and override the doFilter method. First, we cast the ServletRequest to an HttpServletRequest to access the request URI. Then, we check if the URI ends with a slash. If it does, we remove the trailing slash using a new CustomHttpServletRequestWrapper, a private static class extending HttpServletRequestWrapper. This class overrides the getRequestURI and getRequestURL methods to return the modified URI and URL.

Lastly, to apply our custom filter to all endpoints, we can register it using a FilterRegistrationBean with a URL pattern of “/*”. Here is an example:

@Configuration
public class WebConfig {

    @Bean
    public Filter trailingSlashRedirectFilter() {
        return new TrailingSlashRedirectFilter();
    }

    @Bean
    public FilterRegistrationBean<Filter> trailingSlashFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(trailingSlashRedirectFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

Note that applying the filter to all endpoints may have performance implications and can cause unexpected behavior if we have custom endpoints that do not follow standard RESTful URL patterns. In general, applying filters only to the endpoints that require them is recommended.

Finally, let’s look at a couple of tests with the filter in place:

private static final String BASEURL = "/some";

@Autowired
MockMvc mvc;

@Test
public void testGreeting() throws Exception {
    mvc.perform(get(BASEURL + "/greeting").accept(MediaType.APPLICATION_JSON_VALUE))
      .andExpect(status().isOk())
      .andExpect(content().string("Hello"));
}

@Test
public void testGreetingTrailingSlashWithFilter() throws Exception {
    mvc.perform(get(BASEURL + "/greeting/").accept(MediaType.APPLICATION_JSON_VALUE))
      .andExpect(status().isOk())
      .andExpect(content().string("Hello"));
}

6. Configure Redirect Using a Custom WebFilter

For reactive endpoints, we can create a custom class that implements the WebFilter interface and overrides its filter method:

public class TrailingSlashRedirectFilterReactive implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();

        if (path.endsWith("/")) {
            String newPath = path.substring(0, path.length() - 1);
            ServerHttpRequest newRequest = request.mutate().path(newPath).build();
            return chain.filter(exchange.mutate().request(newRequest).build());
        }

        return chain.filter(exchange);
    }
}

First, we extract the request from the ServerWebExchange parameter. We use the getPath method to get the incoming request’s path and check if it ends with a slash. If it does, we remove the trailing slash and create a new ServerHttpRequest using the mutate method. We then pass the modified exchange object to the filter method on the WebFilterChain parameter. We call the filter method with the original exchange object if the path doesn’t end with a slash.

To register the WebFilter, we annotate it with @Component, and Spring Boot will automatically register it with the appropriate WebFilterChain.

To specify the paths for which the custom TrailingSlashRedirectFilterReactive should apply, we can use the @WebFilter annotation and set the urlPatterns attribute to a list of URL patterns.

Finally, let’s look at a couple of tests with the filter in place:

private static final String BASEURL = "/some/reactive";

@Autowired
private WebTestClient webClient;

@Test
public void testGreeting() {
    webClient.get().uri( BASEURL + "/greeting")
      .exchange()
      .expectStatus().isOk()
      .expectBody().consumeWith(result -> {
          String responseBody = new String(result.getResponseBody());
          assertTrue(responseBody.contains("Hello reactive"));
      });
}
   
@Test
public void testGreetingTrailingSlashWithFilter() {
    webClient.get().uri(BASEURL +  "/greeting/")
      .exchange()
      .expectStatus().isOk()
      .expectBody().consumeWith(result -> {
          String responseBody = new String(result.getResponseBody());
          assertTrue(responseBody.contains("Hello reactive"));
      });
}

7. Configure Redirect Through a Proxy

Redirecting requests from URLs that end with a trailing slash to URLs without a trailing slash is a common task when configuring web servers. This can help ensure that all URLs on your site have a consistent structure and improve search engine optimization (SEO).

Moreover, most web servers have built-in support for URL redirection. This support can be used to redirect requests with trailing slashes to URLs without trailing slashes.

Let’s explore how to configure redirects using a proxy for two popular web servers: Apache and Nginx.

7.1. Nginx

location / {
    if ($request_uri ~ ^(.+)/$) {
        return 301 $1;
    }
    
    proxy_pass http://localhost:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

In this example, we add the if block to the root location block—moreover, the if block checks if the request URI ends with a trailing slash. If the URI ends with a trailing slash, it redirects the request to the same URI without the trailing slash using a 301 redirect. The $request_uri is a predefined Nginx variable that contains the original request URI as received from the client, including the query string (if any). The regular expression “^(.+)/$” has a capture group “(.+)”, which captures any sequence of characters followed by a trailing slash. The $1 in the return directive refers to the first captured group, i.e., the matched URI without the trailing slash.

We then use the proxy_pass directive to specify the URL of the backend server that handles the request and use the proxy_set_header directive to set the necessary headers. Note that we need to replace the URL with the actual URL of the backend server.

7.2. Apache

RewriteEngine On
RewriteRule ^(.+)/$ $1 [L,R=301]

ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/

In this example, we use a RewriteRule with the regular expression we used in the Nginx configuration as well. When the RewriteRule is executed, it replaces the matched URL with a trailing slash with the value captured by the first group (the URL without the trailing slash) and performs a 301 redirect.

We then use the ProxyPass and ProxyPassReverse directives to specify the URL of the backend server that handles the request. We can add this configuration within the <VirtualHost> block, which applies it to the entire site.

8. Conclusion

In this article, we discussed Spring Boot 3’s deprecation of the trailing slash matching configuration option, which significantly impacts URL mapping in the framework, requiring some effort but providing a stable and consistent foundation for applications. By understanding this change and updating our applications accordingly, we can ensure a seamless and consistent user experience.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)