diff --git a/spring-boot-modules/spring-boot-3-url-matching/README.md b/spring-boot-modules/spring-boot-3-url-matching/README.md
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/README.md
@@ -0,0 +1 @@
+
diff --git a/spring-boot-modules/spring-boot-3-url-matching/pom.xml b/spring-boot-modules/spring-boot-3-url-matching/pom.xml
new file mode 100644
index 0000000000..ca8213e1cf
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+ spring-boot-3
+ 0.0.1-SNAPSHOT
+ spring-boot-3
+ URL Matching in Spring Boot
+
+
+ com.baeldung
+ parent-boot-3
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-3
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-hateoas
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.springframework
+ spring-test
+ 6.0.6
+ test
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ io.projectreactor
+ reactor-core
+ 3.5.4
+
+
+ io.projectreactor
+ reactor-test
+ 3.5.4
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+ 3.0.0-M7
+
+
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java
new file mode 100644
index 0000000000..914f2e1e53
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+public class URLMatchingApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(URLMatchingApplication.class, args);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java
new file mode 100644
index 0000000000..d4b70c63fa
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java
@@ -0,0 +1,31 @@
+package com.baeldung.sample.config;
+
+import com.baeldung.sample.filters.TrailingSlashRedirectFilterReactive;
+import jakarta.servlet.Filter;
+import org.springframework.web.server.WebFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.baeldung.sample.filters.TrailingSlashRedirectFilter;
+
+
+
+@Configuration
+public class WebConfig {
+ @Bean
+ public WebFilter trailingSlashRedirectReactiveFilter() {
+ return new TrailingSlashRedirectFilterReactive();
+ }
+ @Bean
+ public Filter trailingSlashRedirectFilter() {
+ return new TrailingSlashRedirectFilter();
+ }
+ @Bean
+ public FilterRegistrationBean trailingSlashFilter() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(trailingSlashRedirectFilter());
+ registrationBean.addUrlPatterns("/*");
+ return registrationBean;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java
new file mode 100644
index 0000000000..6db83b73f2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java
@@ -0,0 +1,52 @@
+package com.baeldung.sample.filters;
+
+import java.io.IOException;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.springframework.stereotype.Component;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+
+@Component
+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;
+ }
+ }
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java
new file mode 100644
index 0000000000..f983d81441
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java
@@ -0,0 +1,27 @@
+package com.baeldung.sample.filters;
+
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+
+import reactor.core.publisher.Mono;
+
+@Component
+public class TrailingSlashRedirectFilterReactive implements WebFilter {
+
+ @Override
+ public Mono 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);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java
new file mode 100644
index 0000000000..5c98ed764e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java
@@ -0,0 +1,19 @@
+package com.baeldung.sample.resources;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class GreetingsController {
+
+ @GetMapping("/some/greeting")
+ public String greeting() {
+ return "Hello";
+ }
+
+ @GetMapping("/some/greeting/")
+ public String greetingTrailingSlash() {
+ return "Hello with slash";
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java
new file mode 100644
index 0000000000..fde353ac43
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java
@@ -0,0 +1,20 @@
+package com.baeldung.sample.resources;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import reactor.core.publisher.Mono;
+
+@RestController
+public class GreetingsControllerReactive {
+
+ @GetMapping("/some/reactive/greeting")
+ public Mono greeting() {
+ return Mono.just("Hello reactive");
+ }
+
+ @GetMapping("/some/reactive/greeting/")
+ public Mono greetingTrailingSlash() {
+ return Mono.just("Hello with slash reactive");
+ }
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml
new file mode 100644
index 0000000000..8d047336f6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml
@@ -0,0 +1,7 @@
+spring:
+ mvc:
+ throw-exception-if-no-handler-found: true
+ jackson:
+ deserialization:
+ FAIL_ON_UNKNOWN_PROPERTIES: true
+ property-naming-strategy: SNAKE_CASE
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java
new file mode 100644
index 0000000000..754ef4aafe
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java
@@ -0,0 +1,45 @@
+package com.baeldung.sample.resources;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+@WebMvcTest(controllers = GreetingsController.class)
+class GreetingsControllerApiIntegrationTest {
+
+ private static final String BASEURL = "/some";
+ private static final String DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_VALUE;
+
+ @Autowired
+ MockMvc mvc;
+
+ @Test
+ public void testGreeting() throws Exception {
+ mvc.perform(get(BASEURL + "/greeting").accept(DEFAULT_MEDIA_TYPE))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Hello"));
+ }
+
+ @Test
+ @Disabled("Disabled while TrailingSlashRedirectFilter is in use.")
+ public void testGreetingTrailingSlash() throws Exception {
+ mvc.perform(get(BASEURL + "/greeting/").accept(DEFAULT_MEDIA_TYPE))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Hello with slash"));
+ }
+
+ @Test
+ public void testGreetingTrailingSlashWithFilter() throws Exception {
+ mvc.perform(get(BASEURL + "/greeting/").accept(DEFAULT_MEDIA_TYPE))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Hello"));
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java
new file mode 100644
index 0000000000..9d5e439a74
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.sample.resources;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@WebFluxTest
+public class GreetingsControllerReactiveApiIntegrationTest {
+ 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
+ @Disabled("Disabled while TrailingSlashRedirectFilter is in use.")
+ public void testGreetingTrailingSlash() {
+ webClient.get().uri(BASEURL + "/greeting/")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody().consumeWith(result -> {
+ String responseBody = new String(result.getResponseBody());
+ assertTrue(responseBody.contains("Hello with slash 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"));
+ });
+ }
+}