diff --git a/spring-web-modules/pom.xml b/spring-web-modules/pom.xml
index a9970e85c0..d513822ea3 100644
--- a/spring-web-modules/pom.xml
+++ b/spring-web-modules/pom.xml
@@ -28,6 +28,7 @@
spring-mvc-forms-thymeleaf
spring-mvc-java
spring-mvc-java-2
+ spring-mvc-java-3
spring-mvc-velocity
spring-mvc-views
spring-mvc-webflow
diff --git a/spring-web-modules/spring-mvc-java-3/README.md b/spring-web-modules/spring-mvc-java-3/README.md
new file mode 100644
index 0000000000..7d843af9ea
--- /dev/null
+++ b/spring-web-modules/spring-mvc-java-3/README.md
@@ -0,0 +1 @@
+### Relevant Articles:
diff --git a/spring-web-modules/spring-mvc-java-3/pom.xml b/spring-web-modules/spring-mvc-java-3/pom.xml
new file mode 100644
index 0000000000..3a49a5f9af
--- /dev/null
+++ b/spring-web-modules/spring-mvc-java-3/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+ 4.0.0
+ spring-mvc-java-3
+ 0.1-SNAPSHOT
+ spring-mvc-java-3
+ war
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ org.springframework
+ spring-webmvc
+
+
+
+
+ spring-mvc-java-3
+
+
+ src/main/resources
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-web-modules/spring-mvc-java-3/src/main/java/com/baeldung/filters/CacheRequestContentFilter.java b/spring-web-modules/spring-mvc-java-3/src/main/java/com/baeldung/filters/CacheRequestContentFilter.java
new file mode 100644
index 0000000000..907ec24700
--- /dev/null
+++ b/spring-web-modules/spring-mvc-java-3/src/main/java/com/baeldung/filters/CacheRequestContentFilter.java
@@ -0,0 +1,23 @@
+package com.baeldung.filters;
+
+import org.springframework.web.util.ContentCachingRequestWrapper;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+@WebFilter(urlPatterns = "/*")
+public class CacheRequestContentFilter implements Filter {
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
+ if (request instanceof HttpServletRequest) {
+ String contentType = request.getContentType();
+ if (contentType == null || !contentType.contains("multipart/form-data")) {
+ request = new ContentCachingRequestWrapper((HttpServletRequest) request);
+ }
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/spring-web-modules/spring-mvc-java-3/src/test/java/com/baeldung/filters/HttpRequestUnitTest.java b/spring-web-modules/spring-mvc-java-3/src/test/java/com/baeldung/filters/HttpRequestUnitTest.java
new file mode 100644
index 0000000000..cd369d9118
--- /dev/null
+++ b/spring-web-modules/spring-mvc-java-3/src/test/java/com/baeldung/filters/HttpRequestUnitTest.java
@@ -0,0 +1,47 @@
+package com.baeldung.filters;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.util.ContentCachingRequestWrapper;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class HttpRequestUnitTest {
+
+ @Test
+ public void givenHttpServletRequest_whenCalling_getReaderAfter_getInputStream_thenThrowIllegalStateException() throws IOException {
+ HttpServletRequest request = new MockHttpServletRequest();
+ try (ServletInputStream ignored = request.getInputStream()) {
+ IllegalStateException exception = assertThrows(IllegalStateException.class, request::getReader);
+ assertEquals("Cannot call getReader() after getInputStream() has already been called for the current request",
+ exception.getMessage());
+ }
+ }
+
+ @Test
+ public void givenServletRequest_whenDoFilter_thenCanCallBoth() throws ServletException, IOException {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ MockHttpServletResponse res = new MockHttpServletResponse();
+ MockFilterChain chain = new MockFilterChain();
+
+ Filter filter = new CacheRequestContentFilter();
+ filter.doFilter(req, res, chain);
+
+ ServletRequest request = chain.getRequest();
+ assertTrue(request instanceof ContentCachingRequestWrapper);
+
+ // now we can call both getInputStream() and getReader()
+ request.getInputStream();
+ request.getReader();
+ }
+
+}