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
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
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).