diff --git a/spring-web-modules/spring-mvc-basics-3/README.md b/spring-web-modules/spring-mvc-basics-3/README.md
index 074c60152c..86c26d8a60 100644
--- a/spring-web-modules/spring-mvc-basics-3/README.md
+++ b/spring-web-modules/spring-mvc-basics-3/README.md
@@ -7,10 +7,7 @@ This module contains articles about Spring MVC
- [A Custom Data Binder in Spring MVC](https://www.baeldung.com/spring-mvc-custom-data-binder)
- [Validating Lists in a Spring Controller](https://www.baeldung.com/spring-validate-list-controller)
- [Spring Validation Message Interpolation](https://www.baeldung.com/spring-validation-message-interpolation)
-- [Using a Slash Character in Spring URLs](https://www.baeldung.com/spring-slash-character-in-url)
- [Using Enums as Request Parameters in Spring](https://www.baeldung.com/spring-enum-request-param)
-- [Excluding URLs for a Filter in a Spring Web Application](https://www.baeldung.com/spring-exclude-filter)
- [Guide to Flash Attributes in a Spring Web Application](https://www.baeldung.com/spring-web-flash-attributes)
-- [Handling URL Encoded Form Data in Spring REST](https://www.baeldung.com/spring-url-encoded-form-data)
- [Reading HttpServletRequest Multiple Times in Spring](https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times)
- More articles: [[<-- prev]](/spring-mvc-basics-2)[[more -->]](/spring-mvc-basics-4)
diff --git a/spring-web-modules/spring-web-url/README.md b/spring-web-modules/spring-web-url/README.md
new file mode 100644
index 0000000000..41b479337b
--- /dev/null
+++ b/spring-web-modules/spring-web-url/README.md
@@ -0,0 +1,8 @@
+## Spring Web URL
+
+This module contains articles about Spring MVC
+
+## Relevant articles:
+- [Using a Slash Character in Spring URLs](https://www.baeldung.com/spring-slash-character-in-url)
+- [Excluding URLs for a Filter in a Spring Web Application](https://www.baeldung.com/spring-exclude-filter)
+- [Handling URL Encoded Form Data in Spring REST](https://www.baeldung.com/spring-url-encoded-form-data)
diff --git a/spring-web-modules/spring-web-url/pom.xml b/spring-web-modules/spring-web-url/pom.xml
new file mode 100644
index 0000000000..0a10afad08
--- /dev/null
+++ b/spring-web-modules/spring-web-url/pom.xml
@@ -0,0 +1,138 @@
+
+
+ 4.0.0
+ spring-web-url
+ spring-web-url
+ war
+ Demo project for Spring Boot
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ javax.persistence
+ javax.persistence-api
+ ${jpa.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ org.subethamail
+ subethasmtp
+ ${subethasmtp.version}
+ test
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
+
+
+
+ ${project.artifactId}
+
+
+ src/main/resources
+ true
+
+ **/conf.properties
+
+
+
+
+
+
+
+ autoconfiguration
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration-test
+
+ test
+
+
+
+ **/*LiveTest.java
+ **/*IntegrationTest.java
+ **/*IntTest.java
+
+
+ **/AutoconfigurationTest.java
+
+
+
+
+
+
+ json
+
+
+
+
+
+
+
+
+
+
+ com.baeldung.exclude_urls_filter.Application
+ 3.1.1
+ 3.3.7-1
+ 2.2
+ 18.0
+ 3.1.7
+ 4.5.8
+
+
+
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/Application.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/Application.java
new file mode 100644
index 0000000000..4fb6938694
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/Application.java
@@ -0,0 +1,17 @@
+package com.baeldung.exclude_urls_filter;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@ComponentScan(basePackages = "com.baeldung.exclude_urls_filter")
+@Configuration
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/FAQController.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/FAQController.java
new file mode 100644
index 0000000000..1463af6bfd
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/FAQController.java
@@ -0,0 +1,32 @@
+package com.baeldung.exclude_urls_filter.controller;
+
+import com.baeldung.exclude_urls_filter.service.FAQService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class FAQController {
+
+ private final FAQService faqService;
+
+ @Autowired
+ public FAQController(FAQService faqService) {
+ this.faqService = faqService;
+ }
+
+ @RequestMapping(value = "/faq/helpline", method = RequestMethod.GET)
+ public ResponseEntity getHelpLineNumber() {
+ String helplineNumber = faqService.getHelpLineNumber();
+ if (helplineNumber != null) {
+ return new ResponseEntity(helplineNumber, HttpStatus.OK);
+ } else {
+ return new ResponseEntity("Unavailable", HttpStatus.NOT_FOUND);
+ }
+ }
+
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/Ping.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/Ping.java
new file mode 100644
index 0000000000..c8a0723ba6
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/controller/Ping.java
@@ -0,0 +1,22 @@
+package com.baeldung.exclude_urls_filter.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class Ping {
+
+ @RequestMapping(value = "/health", method = RequestMethod.GET)
+ public ResponseEntity pingGet() {
+ return new ResponseEntity("pong", HttpStatus.OK);
+ }
+
+ @RequestMapping(value = "/health", method = RequestMethod.POST)
+ public ResponseEntity pingPost() {
+ return new ResponseEntity("pong", HttpStatus.OK);
+ }
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/FilterRegistrationConfig.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/FilterRegistrationConfig.java
new file mode 100644
index 0000000000..ff99b4cc25
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/FilterRegistrationConfig.java
@@ -0,0 +1,26 @@
+package com.baeldung.exclude_urls_filter.filter;
+
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FilterRegistrationConfig {
+
+ @Bean
+ public FilterRegistrationBean logFilter() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new LogFilter());
+ registrationBean.addUrlPatterns("/health", "/faq/*");
+ return registrationBean;
+ }
+
+
+ @Bean
+ public FilterRegistrationBean headerValidatorFilter() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new HeaderValidatorFilter());
+ registrationBean.addUrlPatterns("*");
+ return registrationBean;
+ }
+}
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/HeaderValidatorFilter.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/HeaderValidatorFilter.java
new file mode 100644
index 0000000000..d6c1777326
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/HeaderValidatorFilter.java
@@ -0,0 +1,33 @@
+package com.baeldung.exclude_urls_filter.filter;
+
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Order(1)
+public class HeaderValidatorFilter extends OncePerRequestFilter {
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain)
+ throws ServletException,
+ IOException {
+ String countryCode = request.getHeader("X-Country-Code");
+ if (!"US".equals(countryCode)) {
+ response.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid Locale");
+ return;
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+ String path = request.getRequestURI();
+ return "/health".equals(path);
+ }
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/LogFilter.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/LogFilter.java
new file mode 100644
index 0000000000..fcde4f7f8f
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/filter/LogFilter.java
@@ -0,0 +1,25 @@
+package com.baeldung.exclude_urls_filter.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Order(1)
+public class LogFilter extends OncePerRequestFilter {
+ private final Logger logger = LoggerFactory.getLogger(LogFilter.class);
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ String path = request.getRequestURI();
+ String contentType = request.getContentType();
+ logger.info("Request URL path : {}, Request content type: {}", path, contentType);
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQService.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQService.java
new file mode 100644
index 0000000000..a2949ea0a2
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQService.java
@@ -0,0 +1,5 @@
+package com.baeldung.exclude_urls_filter.service;
+
+public interface FAQService {
+ String getHelpLineNumber();
+}
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQServiceImpl.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQServiceImpl.java
new file mode 100644
index 0000000000..6f841e4ec1
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/exclude_urls_filter/service/FAQServiceImpl.java
@@ -0,0 +1,15 @@
+package com.baeldung.exclude_urls_filter.service;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class FAQServiceImpl implements FAQService {
+
+ private static final String HELPLINE_NUMBER = "+1 888-777-66";
+
+ @Override
+ public String getHelpLineNumber() {
+ return HELPLINE_NUMBER;
+ }
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/Application.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/Application.java
new file mode 100644
index 0000000000..34c14141b0
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/Application.java
@@ -0,0 +1,14 @@
+package com.baeldung.form_submission;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+@SpringBootApplication
+public class Application extends SpringBootServletInitializer {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/controllers/FeedbackForm.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/controllers/FeedbackForm.java
new file mode 100644
index 0000000000..791fc75cef
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/controllers/FeedbackForm.java
@@ -0,0 +1,42 @@
+package com.baeldung.form_submission.controllers;
+
+import com.baeldung.form_submission.model.Feedback;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@Controller
+public class FeedbackForm {
+
+ @GetMapping(path = "/feedback")
+ public String getFeedbackForm(Model model) {
+ Feedback feedback = new Feedback();
+ model.addAttribute("feedback", feedback);
+ return "feedback";
+ }
+
+ @PostMapping(
+ path = "/web/feedback",
+ consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
+ public String handleBrowserSubmissions(Feedback feedback) throws Exception {
+ // Save feedback data
+ return "redirect:/feedback/success";
+ }
+
+ @GetMapping("/feedback/success")
+ public ResponseEntity getSuccess() {
+ return new ResponseEntity("Thank you for submitting feedback.", HttpStatus.OK);
+ }
+
+ @PostMapping(
+ path = "/feedback",
+ consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
+ public ResponseEntity handleNonBrowserSubmissions(@RequestParam MultiValueMap paramMap) throws Exception {
+ // Save feedback data
+ return new ResponseEntity("Thank you for submitting feedback", HttpStatus.OK);
+ }
+}
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/model/Feedback.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/model/Feedback.java
new file mode 100644
index 0000000000..f8d416460c
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/form_submission/model/Feedback.java
@@ -0,0 +1,23 @@
+package com.baeldung.form_submission.model;
+
+public class Feedback {
+ private String emailId;
+ private String comment;
+
+ public String getEmailId() {
+ return this.emailId;
+ }
+
+ public void setEmailId(String emailId) {
+ this.emailId = emailId;
+ }
+
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+}
diff --git a/spring-web-modules/spring-web-url/src/main/resources/application.properties b/spring-web-modules/spring-web-url/src/main/resources/application.properties
new file mode 100644
index 0000000000..fcdaabe007
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+spring.mail.host=localhost
+spring.mail.port=8025
+
+spring.thymeleaf.cache=false
+spring.thymeleaf.enabled=true
+spring.thymeleaf.prefix=classpath:/templates/
+spring.thymeleaf.suffix=.html
diff --git a/spring-web-modules/spring-web-url/src/main/resources/templates/feedback.html b/spring-web-modules/spring-web-url/src/main/resources/templates/feedback.html
new file mode 100644
index 0000000000..4b6a487fc2
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/main/resources/templates/feedback.html
@@ -0,0 +1,41 @@
+
+
+
+ Poetry Contest: Submission
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-web-modules/spring-web-url/src/test/java/com/baeldung/spring/slash/SlashParsingControllerIntTest.java b/spring-web-modules/spring-web-url/src/test/java/com/baeldung/spring/slash/SlashParsingControllerIntTest.java
new file mode 100644
index 0000000000..d81b34f7bf
--- /dev/null
+++ b/spring-web-modules/spring-web-url/src/test/java/com/baeldung/spring/slash/SlashParsingControllerIntTest.java
@@ -0,0 +1,87 @@
+package com.baeldung.spring.slash;
+
+import static org.junit.Assert.assertEquals;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.net.URI;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+@AutoConfigureMockMvc
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class SlashParsingControllerIntTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ public void whenUsingPathVariablemWithoutSlashes_thenStatusOk() throws Exception {
+ final String stringWithoutSlashes = "noslash";
+
+ MvcResult mvcResult = mockMvc.perform(get("/slash/mypaths/" + stringWithoutSlashes))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ assertEquals(stringWithoutSlashes, mvcResult.getResponse()
+ .getContentAsString());
+ }
+
+ @Test
+ public void whenUsingPathVariableWithSlashes_thenStatusNotFound() throws Exception {
+ final String stringWithSlashes = "url/with/slashes";
+
+ mockMvc.perform(get("/slash/mypaths/" + stringWithSlashes))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void givenAllFallbackEndpoint_whenUsingPathWithSlashes_thenStatusOk() throws Exception {
+ final String stringWithSlashes = "url/for/testing/purposes";
+
+ MvcResult mvcResult = mockMvc.perform(get("/slash/all/" + stringWithSlashes))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ assertEquals(stringWithSlashes, mvcResult.getResponse()
+ .getContentAsString());
+ }
+
+ @Test
+ public void givenAllFallbackEndpoint_whenUsingConsecutiveSlashes_thenPathNormalized() throws Exception {
+ final String stringWithSlashes = "http://myurl.com";
+
+ MvcResult mvcResult = mockMvc.perform(get("/slash/all/" + stringWithSlashes))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ String stringWithSlashesNormalized = URI.create("/slash/all/" + stringWithSlashes)
+ .normalize()
+ .toString()
+ .split("/slash/all/")[1];
+
+ assertEquals(stringWithSlashesNormalized, mvcResult.getResponse()
+ .getContentAsString());
+ }
+
+ @Test
+ public void whenUsingSlashesInQueryParam_thenParameterAccepted() throws Exception {
+ final String stringWithSlashes = "url/for////testing/purposes";
+
+ MvcResult mvcResult = mockMvc.perform(get("/slash/all").param("param", stringWithSlashes))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ assertEquals(stringWithSlashes, mvcResult.getResponse()
+ .getContentAsString());
+ }
+
+}