Close

Spring MVC - Customized Locale selection using CookieLocaleResolver

[Last Updated: Aug 27, 2026]

This is another option to remember a customized locale selection.

java.lang.ObjectObjectorg.springframework.web.util.CookieGeneratorCookieGeneratororg.springframework.web.servlet.i18n.CookieLocaleResolverCookieLocaleResolverorg.springframework.web.servlet.LocaleContextResolverLocaleContextResolverLogicBig

CookieLocaleResolver internally persists a custom locale and/or time zone information as a browser cookie. It uses javax.servlet.http.Cookie to accomplish that. This LocaleResolver is preferred over SessionLocaleResolver when the application needs to be stateless, or when locale information should be persisted beyond the HttpSession lifetime.

As discussed in the SessionLocaleResolver example, there can be many different scenarios where a Locale instance should be remembered on the server side. Here we are going to use the same scenario as in the last example, where LocaleChangeInterceptor retrieves the specified HTTP parameter value, parses it into a Locale instance, and then hands it over to the underlying LocaleResolver (CookieLocaleResolver in this case). At this point, CookieLocaleResolver adds the locale information as a cookie to the HTTP response.

Example


Configuration class

package com.logicbig.example;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.time.Duration;
import java.util.Locale;

@EnableWebMvc
@Configuration
@ComponentScan
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver r =
                new CookieLocaleResolver("localInfo");
        r.setDefaultLocale(Locale.US);
        r.setCookieMaxAge(Duration.ofDays(1));
        return r;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor l = new LocaleChangeInterceptor();
        l.setParamName("localeCode");
        registry.addInterceptor(l);
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.jsp("/WEB-INF/pages/", ".jsp");
    }

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasenames("msgs/msg");
        source.setDefaultEncoding("UTF-8");
        return source;
    }
}
Note: Spring MVC 6 introduced following changes to the CookieLocaleResolver class.
  1. setCookieName(String) method has been removed and is now replaced by a constructor that accepts the cookie name directly: CookieLocaleResolver(String cookieName).
  2. The setCookieMaxAge(Integer) method is superseded by setCookieMaxAge(Duration), which leverages the java.time.Duration API for more precise and flexible expiry configurations.
  3. The determineDefaultLocale(HttpServletRequest request) method deprecated (removed in Spring 7) and its internal logic has been internalized; it is now superseded by the functional setDefaultLocaleFunction(Function) method, which provides a more modern and customizable way to resolve the default locale. for example:
    @Bean
    public CookieLocaleResolver localeResolver() {
        CookieLocaleResolver resolver = new CookieLocaleResolver();
        resolver.setDefaultLocaleFunction(request -> {
            String header = request.getHeader("X-Custom-Header");
            return header != null ? Locale.forLanguageTag(header) : Locale.US;
        });
        return resolver;
    }


  4. Controller

    package com.logicbig.example;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import java.util.LinkedHashMap;
    import java.util.Locale;
    import java.util.Map;
    
    @Controller
    public class TheController {
    
        @RequestMapping(value = "/",
                  method = {RequestMethod.POST, RequestMethod.GET})
        public String handleGet (Model model, Locale locale) {
    
            FormData formData = new FormData();
            formData.setLocaleCode(locale.toString());
            model.addAttribute("formData", formData);
    
            Map<String, String> localeChoices = new LinkedHashMap<>();
            Locale l = Locale.US;
            localeChoices.put(l.toString(), l.getDisplayLanguage());
            l = Locale.GERMANY;
            localeChoices.put(l.toString(), l.getDisplayLanguage());
            l = Locale.FRANCE;
            localeChoices.put(l.toString(), l.getDisplayLanguage());
            model.addAttribute("localeChoices", localeChoices);
    
            return "main";
        }
    
        @RequestMapping(value = "/page1")
        public String handlePage1 () {
            return "page1";
        }
    
        @RequestMapping(value = "/page2")
        public String handlePage2 () {
            return "page2";
        }
    
    
        public static class FormData {
            private String localeCode;
    
            public String getLocaleCode () {
                return localeCode;
            }
    
            public void setLocaleCode (String localeCode) {
                this.localeCode = localeCode;
            }
        }
    }

    The rest of the artifacts remain the same as in the last example. The output and flow are also the same.

    After changing the language in the dropdown, a cookie will be created in the browser.

    In the Google Chrome browser (right-click on the page > Inspect > Application (top toolbar) > Storage (left tree) > Cookies > http://localhost:8080):


    Integration Test

    package com.logicbig.example;
    
    import jakarta.servlet.http.Cookie;
    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.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.MvcResult;
    import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    import static org.hamcrest.Matchers.hasProperty;
    import static org.hamcrest.Matchers.is;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    
    @ExtendWith(SpringExtension.class)
    @WebAppConfiguration
    @ContextConfiguration(classes = WebConfig.class)
    public class ControllerTest {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mockMvc;
    
        @BeforeEach
        public void setup() {
            DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
            this.mockMvc = builder.build();
        }
    
        @Test
        public void localeSetByPostPersistsOnSubsequentGet() throws Exception {
    
            MvcResult result =
                    mockMvc.perform(post("/")
                                            .param("localeCode",
                                                   "de_DE")
                           )
                           .andExpect(status().isOk())
                           .andExpect(cookie().exists("localInfo"))
                           .andExpect(model().attribute(
                                   "formData",
                                   hasProperty("localeCode",
                                               is("de_DE"))))
                           .andReturn();
    
            mockMvc.perform(get("/").cookie(
                           result.getResponse().getCookies())
                   )
                   .andExpect(status().isOk())
                   .andExpect(model().attribute("formData",
                                                hasProperty("localeCode",
                                                            is("de_DE"))));
    
            mockMvc.perform(get("/page1").cookie(
                           result.getResponse().getCookies()))
                   .andExpect(status().isOk())
                   .andExpect(view().name("page1"))
                   .andExpect(forwardedUrl("/WEB-INF/pages/page1.jsp"));
        }
    }
    
    mvn clean test -Dtest="ControllerTest"

    Output

    $ mvn clean test -Dtest="ControllerTest"
    [INFO] Scanning for projects...
    [INFO]
    [INFO] --------< com.logicbig.example:cookie-locale-resolver-example >---------
    [INFO] Building cookie-locale-resolver-example 1.0-SNAPSHOT
    [INFO] from pom.xml
    [INFO] --------------------------------[ war ]---------------------------------
    [INFO]
    [INFO] --- clean:3.2.0:clean (default-clean) @ cookie-locale-resolver-example ---
    [INFO] Deleting D:\example-projects\spring-mvc\cookie-locale-resolver-example\target
    [INFO]
    [INFO] --- resources:3.3.1:resources (default-resources) @ cookie-locale-resolver-example ---
    [INFO] Copying 3 resources from src\main\resources to target\classes
    [INFO]
    [INFO] --- compiler:3.3:compile (default-compile) @ cookie-locale-resolver-example ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 3 source files to D:\example-projects\spring-mvc\cookie-locale-resolver-example\target\classes
    [INFO]
    [INFO] --- resources:3.3.1:testResources (default-testResources) @ cookie-locale-resolver-example ---
    [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\cookie-locale-resolver-example\src\test\resources
    [INFO]
    [INFO] --- compiler:3.3:testCompile (default-testCompile) @ cookie-locale-resolver-example ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 1 source file to D:\example-projects\spring-mvc\cookie-locale-resolver-example\target\test-classes
    [INFO]
    [INFO] --- surefire:3.2.5:test (default-test) @ cookie-locale-resolver-example ---
    [INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
    [INFO]
    [INFO] -------------------------------------------------------
    [INFO] T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running com.logicbig.example.ControllerTest
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.756 s -- in com.logicbig.example.ControllerTest
    [INFO]
    [INFO] Results:
    [INFO]
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 2.882 s
    [INFO] Finished at: 2026-08-27T18:48:42+08:00
    [INFO] ------------------------------------------------------------------------

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • JDK 25
  • Maven 3.9.11

Spring MVC - CookieLocaleResolver Example Select All Download
  • cookie-locale-resolver-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • WebConfig.java
          • resources
            • msgs
          • webapp
            • WEB-INF
              • pages
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join