Internationalization (i18n)
👉 Complete Guide to Mastering i18n using Resource Bundles, Locale Resolution, and Message Handling in Spring Boot.
What is i18n ?
i18n or Internationalization, is the process of designing your application to adapt to various languages and regions without requiring application changes.
In a Spring Boot microservice, this is crucial for creating a global user experience. Spring’s MessageSource abstraction simplifies this process by resolving messages (text strings) based on a user’s locale.
Table of Contents
- What is i18n ?
- Table of Contents
- Maven Dependencies
- How to enable i18n ?
- Resource Files for Messages
- How Locale is Determined ?
- Message Resolution Flow
- Benefits and Drawbacks
- Best practices
Maven Dependencies
The necessary API for i18n, primarily the MessageSource interface, is part of the spring-context dependency. Which is included as part of standard web dependency spring-boot-starter-web.
<!-- pom.xml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
How to enable i18n ?
The implementation involves autowiring the MessageSource bean, which Spring Boot auto-configures. The key method is getMessage(), where we provide the message key, optional arguments, a default message, and the desired Locale.
The Locale is typically extracted from the incoming HTTP request’s Accept-Language header using LocaleContextHolder.getLocale().
Controller
import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldI18n {
private final MessageSource messageSource;
// Use constructor injection for MessageSource (Best Practice)
public HelloWorldI18n(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/say-hello-i18n")
public String sayHello() {
// Get the Locale from the current HTTP Request's Accept-Language header
var locale = LocaleContextHolder.getLocale();
// Resolve the message key based on the determined locale
return this.messageSource.getMessage(
"good.morning", // The message key
null, // Arguments for parametric messages
"Default - Good Morning", // Fallback message if key not found
locale // The target locale
);
}
}
Resource Files for Messages
Messages are stored in standard Java .properties files following the naming convention: messages[-<locale>].properties.
Spring Boot automatically looks for these files in the classpath (typically in src/main/resources/).
File Structure
| File Name | Locale | Description |
|---|---|---|
messages.properties | Default (Fallback) | Used when no specific locale file is found. |
messages_en.properties | English (US/UK) | Used for English-speaking users. |
messages_es.properties | Spanish | Used for Spanish-speaking users. |
messages_de.properties | German | Used for German-speaking users. (Note: ger is non-standard) |
Message Content
messages.properties
good.morning=Good Morning (Default)
messages_es.properties
good.morning=Buenos días
Project Reference:
Refer the application source code on Github: Internationalization POC App
How Locale is Determined ?
This mechanism relies on Spring reading the value of the Accept-Language HTTP Header sent by the client (e.g., a browser or another microservice). Based on this header (e.g., es, de-DE, en-US), Spring finds the most appropriate messages[-<locale>].properties file.
The default message file prefix is messages and the suffix is .properties. This can be customized in application.properties using spring.messages.basename.
HTTP Request Header
Header:
Accept-Language: es
Action: Spring resolves the message frommessages_es.properties.
Message Resolution Flow
Below diagram illustrates how a request’s locale determines the message resolution.
sequenceDiagram
participant C as Client (e.g., Browser)
participant A as Spring Boot Microservice
participant MS as MessageSource (Internal)
participant RF as Resource Files (.properties)
C->>A: HTTP GET /say-hello-i18n (Header: Accept-Language: es)
A->>A: Get Locale from LocaleContextHolder
A->>MS: messageSource.getMessage("good.morning", ..., locale)
MS->>RF: Look for messages_es.properties
alt File Found
RF-->>MS: Return message "Buenos días"
else File Not Found
RF-->>MS: Look for messages.properties (Default)
RF-->>MS: Return message "Good Morning (Default)"
end
MS-->>A: Resolved Message
A-->>C: HTTP Response (Body: Resolved Message)
Benefits and Drawbacks
| Aspect | Benefits 👍 | Drawbacks 👎 |
|---|---|---|
| Accessibility | Wider market reach and better user experience for global users. | Maintenance Overhead for translating and managing numerous resource files. |
| Code | Clean separation of text content from the Java code (no hardcoded strings). | Requires discipline to use message keys everywhere, not just hardcode strings. |
| Scalability | Easy to add new languages by simply adding a new .properties file. | Context Loss during translation; translators might lack context without proper comments/guidance. |
Best practices
For a robust, production-ready microservice implementation, consider these best practices.
Custom Base Name:Define the base name explicitly inapplication.propertiesto ensure future-proofing and clarity.spring.messages.basename=i18n/messages # This would require placing files inside `src/main/resources/i18n/`.Parametric Messages:Use placeholders in messages for dynamic content.- messages_en.properties:
good.morning.user=Buenos días, {0} - Then pass the user from
getMessageAPI as argument:messageSource.getMessage("good.morning.user", new Object[]{"Vivek"}, ...)
- messages_en.properties:
Externalizing MessageSource:For large microservice deployments or applications requiring real-time updates, consider implementing a customMessageSourcethat loads messages from a centralized database or a configuration service (e.g., Consul, Spring Cloud Config).