Master Spring Boot Internationalization (i18n) in 10 Simple Steps

This guide walks you through configuring Spring Boot for full‑stack internationalization, covering dependency setup, message resource files, application properties, locale resolver and interceptor, controller and service usage, Thymeleaf templates, custom resolvers, testing, and handling static content.

Ray's Galactic Tech
Ray's Galactic Tech
Ray's Galactic Tech
Master Spring Boot Internationalization (i18n) in 10 Simple Steps

Overview

Spring Boot offers robust i18n support that lets developers create multilingual applications with minimal effort. The following steps demonstrate how to configure and use internationalization from scratch.

1. Add Maven dependencies

<dependencies>
    <!-- Web dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thymeleaf template engine -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

2. Create message resource files

Place the following .properties files under src/main/resources: messages.properties – default language messages_zh_CN.properties – Simplified Chinese messages_fr.properties – French

# messages.properties
welcome.message=Welcome to our application!
user.greeting=Hello, {0}
page.title=Home Page
# messages_zh_CN.properties
welcome.message=欢迎使用我们的应用程序!
user.greeting=你好,{0}
page.title=首页
# messages_fr.properties
welcome.message=Bienvenue dans notre application !
user.greeting=Bonjour, {0}
page.title=Page d'accueil

3. Configure i18n settings

# application.properties
spring.messages.basename=messages
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=false

4. Configure locale resolver and interceptor

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    // Default locale = English
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.ENGLISH);
        return slr;
    }
    // Parameter name for switching language, e.g. ?lang=zh_CN
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        return lci;
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

5. Use messages in a controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Locale;

@Controller
public class HomeController {
    @Autowired
    private MessageSource messageSource;

    @GetMapping("/")
    public String home(Model model, Locale locale) {
        String welcomeMessage = messageSource.getMessage("welcome.message", null, locale);
        String userGreeting = messageSource.getMessage("user.greeting", new Object[]{"John"}, locale);
        model.addAttribute("welcomeMessage", welcomeMessage);
        model.addAttribute("userGreeting", userGreeting);
        model.addAttribute("pageTitle", messageSource.getMessage("page.title", null, locale));
        return "home";
    }
}

6. Display messages in a Thymeleaf template

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${pageTitle}">Home Page</title>
</head>
<body>
    <h1 th:text="${welcomeMessage}">Welcome message</h1>
    <p th:text="${userGreeting}">User greeting</p>
    <!-- Language switch links -->
    <div>
        <a th:href="@{/(lang='en')}">English</a> |
        <a th:href="@{/(lang='zh_CN')}">中文</a> |
        <a th:href="@{/(lang='fr')}">Français</a>
    </div>
    <!-- Direct message lookup in template -->
    <p th:text="#{welcome.message}">Welcome message</p>
    <p th:text="#{user.greeting(${'Jane'})}">User greeting</p>
</body>
</html>

7. Advanced: custom locale resolver

import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class CustomLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String langHeader = request.getHeader("Accept-Language");
        if (langHeader != null && !langHeader.isEmpty()) {
            return Locale.forLanguageTag(langHeader);
        }
        return Locale.getDefault();
    }
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        // No implementation needed for read‑only resolver
    }
}

8. Use i18n in the service layer

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import java.util.Locale;

@Service
public class UserService {
    @Autowired
    private MessageSource messageSource;

    public String getLocalizedWelcomeMessage(String username, Locale locale) {
        return messageSource.getMessage("user.greeting", new Object[]{username}, locale);
    }
}

9. Write tests to verify messages

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.MessageSource;
import java.util.Locale;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
public class I18nTest {
    @Autowired
    private MessageSource messageSource;

    @Test
    public void testEnglishMessages() {
        String message = messageSource.getMessage("welcome.message", null, Locale.ENGLISH);
        assertEquals("Welcome to our application!", message);
    }

    @Test
    public void testChineseMessages() {
        String message = messageSource.getMessage("welcome.message", null, Locale.SIMPLIFIED_CHINESE);
        assertEquals("欢迎使用我们的应用程序!", message);
    }
}

10. Internationalize static content

@GetMapping("/about")
public String about(Model model, Locale locale) {
    String content;
    if (Locale.SIMPLIFIED_CHINESE.equals(locale)) {
        content = "关于我们的中文内容...";
    } else {
        content = "About us content in English...";
    }
    model.addAttribute("aboutContent", content);
    return "about";
}

Conclusion

The complete Spring Boot i18n solution includes:

Message resource .properties files for each language.

A locale resolver that determines the current language.

An interceptor that enables ?lang=xx URL parameters.

Message retrieval in controllers, services, and Thymeleaf templates.

Further extensions can store localized content in a database or customize language selection based on user preferences.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootThymeleafi18ninternationalizationlocalization
Ray's Galactic Tech
Written by

Ray's Galactic Tech

Practice together, never alone. We cover programming languages, development tools, learning methods, and pitfall notes. We simplify complex topics, guiding you from beginner to advanced. Weekly practical content—let's grow together!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.