diff --git a/pom.xml b/pom.xml
index 6e6316edd6..6bc56a8ab6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -692,6 +692,7 @@
spring-mvc-forms-jsp
spring-mvc-forms-thymeleaf
spring-mvc-java
+ spring-mvc-java-2
spring-mvc-kotlin
spring-mvc-velocity
@@ -1193,6 +1194,7 @@
spring-mvc-forms-jsp
spring-mvc-forms-thymeleaf
spring-mvc-java
+ spring-mvc-java-2
spring-mvc-kotlin
spring-mvc-velocity
diff --git a/spring-mvc-java-2/.gitignore b/spring-mvc-java-2/.gitignore
new file mode 100644
index 0000000000..83c05e60c8
--- /dev/null
+++ b/spring-mvc-java-2/.gitignore
@@ -0,0 +1,13 @@
+*.class
+
+#folders#
+/target
+/neoDb*
+/data
+/src/main/webapp/WEB-INF/classes
+*/META-INF/*
+
+# Packaged files #
+*.jar
+*.war
+*.ear
\ No newline at end of file
diff --git a/spring-mvc-java-2/pom.xml b/spring-mvc-java-2/pom.xml
new file mode 100644
index 0000000000..d5b7d087ab
--- /dev/null
+++ b/spring-mvc-java-2/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+ spring-mvc-java-2
+ 0.1-SNAPSHOT
+ spring-mvc-java-2
+ war
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../parent-boot-2
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ ${javax.version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.mvc.version}
+
+
+
+
+
+ 4.0.1
+ 5.2.2.RELEASE
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-mvc-java-2/src/main/java/com/baeldung/cache/CacheControlController.java b/spring-mvc-java-2/src/main/java/com/baeldung/cache/CacheControlController.java
new file mode 100644
index 0000000000..5bf095888e
--- /dev/null
+++ b/spring-mvc-java-2/src/main/java/com/baeldung/cache/CacheControlController.java
@@ -0,0 +1,55 @@
+package com.baeldung.cache;
+
+import org.springframework.http.CacheControl;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.context.request.WebRequest;
+
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+
+@Controller
+public class CacheControlController {
+
+ @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
+ public ResponseEntity helloWorld(@PathVariable String name) {
+ CacheControl cacheControl = CacheControl.maxAge(60, TimeUnit.SECONDS)
+ .noTransform()
+ .mustRevalidate();
+ return ResponseEntity.ok()
+ .cacheControl(cacheControl)
+ .body("Hello " + name);
+ }
+
+ @RequestMapping(value = "/home/{name}", method = RequestMethod.GET)
+ public String home(@PathVariable String name, final HttpServletResponse response) {
+ response.addHeader("Cache-Control", "max-age=60, must-revalidate, no-transform");
+ return "home";
+ }
+
+ @RequestMapping(value = "/cache/{name}", method = RequestMethod.GET)
+ public ResponseEntity intercept(@PathVariable String name) {
+ return ResponseEntity.ok().body("Hello " + name);
+ }
+
+ @RequestMapping(value = "/validate/{name}", method = RequestMethod.GET)
+ public ResponseEntity validate(@PathVariable String name, WebRequest request) {
+
+ ZoneId zoneId = ZoneId.of("GMT");
+ long lastModifiedTimestamp = LocalDateTime.of(2020, 02, 4, 19, 57, 45)
+ .atZone(zoneId).toInstant().toEpochMilli();
+
+ if (request.checkNotModified(lastModifiedTimestamp)) {
+ return ResponseEntity.status(304).build();
+ }
+
+ return ResponseEntity.ok().body("Hello " + name);
+ }
+
+
+}
diff --git a/spring-mvc-java-2/src/main/java/com/baeldung/cache/WebConfig.java b/spring-mvc-java-2/src/main/java/com/baeldung/cache/WebConfig.java
new file mode 100644
index 0000000000..728479979c
--- /dev/null
+++ b/spring-mvc-java-2/src/main/java/com/baeldung/cache/WebConfig.java
@@ -0,0 +1,41 @@
+package com.baeldung.cache;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.CacheControl;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.mvc.WebContentInterceptor;
+
+import java.util.concurrent.TimeUnit;
+
+@EnableWebMvc
+@Configuration
+@ComponentScan(basePackages = {"com.baeldung.cache"})
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addViewControllers(final ViewControllerRegistry registry) {
+ registry.addViewController("/").setViewName("index");
+ }
+
+ @Override
+ public void addResourceHandlers(final ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/resources/**").addResourceLocations("/resources/")
+ .setCacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS)
+ .noTransform()
+ .mustRevalidate());
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ WebContentInterceptor interceptor = new WebContentInterceptor();
+ interceptor.addCacheMapping(CacheControl.maxAge(60, TimeUnit.SECONDS)
+ .noTransform()
+ .mustRevalidate(), "/cache/*");
+ registry.addInterceptor(interceptor);
+ }
+}
\ No newline at end of file
diff --git a/spring-mvc-java-2/src/main/webapp/resources/hello.css b/spring-mvc-java-2/src/main/webapp/resources/hello.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/spring-mvc-java-2/src/test/java/com/baeldung/cache/CacheControlControllerIntegrationTest.java b/spring-mvc-java-2/src/test/java/com/baeldung/cache/CacheControlControllerIntegrationTest.java
new file mode 100644
index 0000000000..ef408038ae
--- /dev/null
+++ b/spring-mvc-java-2/src/test/java/com/baeldung/cache/CacheControlControllerIntegrationTest.java
@@ -0,0 +1,80 @@
+package com.baeldung.cache;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.http.HttpHeaders.IF_UNMODIFIED_SINCE;
+
+@ExtendWith(SpringExtension.class)
+@WebAppConfiguration
+@ContextConfiguration(classes = {WebConfig.class, WebConfig.class})
+public class CacheControlControllerIntegrationTest {
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ void setup() throws Exception {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ }
+
+ @Test
+ void whenResponseBody_thenReturnCacheHeader() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/baeldung"))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform"));
+ }
+
+ @Test
+ void whenViewName_thenReturnCacheHeader() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/home/baeldung"))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform"))
+ .andExpect(MockMvcResultMatchers.view().name("home"));
+ }
+
+ @Test
+ void whenStaticResources_thenReturnCacheHeader() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/resources/hello.css"))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform"));
+ }
+
+ @Test
+ void whenInterceptor_thenReturnCacheHeader() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/cache/baeldung"))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform"));
+ }
+
+ @Test
+ void whenValidate_thenReturnCacheHeader() throws Exception {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(IF_UNMODIFIED_SINCE, "Tue, 04 Feb 2020 19:57:25 GMT");
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/validate/baeldung").headers(headers))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().is(304));
+ }
+
+
+
+
+}