Localization in IT is like the translator at a conference: users speak different languages, and the system should respond to them clearly. Imagine you're building an app for a global market. For users from different countries it's important that the interface, error messages, and notifications are adapted to their language and cultural norms.
What do we localize?
- Error messages: for example, instead of the dry
must not be null, a user from Russia should see:The field cannot be empty. - Validation messages: when a form is filled out incorrectly, the warning text should also be in the user's language.
- Other notifications: info about invalid date, number, or email input, etc.
How does localization work in Spring?
Spring provides a powerful tool for localization — MessageSource. This component handles text management and supports multiple language files.
Localization components
- Language files: these are plain
.propertiesfiles with keys and values, for example:messages_en.propertiesfor English.messages_ru.propertiesfor Russian.
- MessageSource: a Bean that looks up the right file depending on the current locale.
- LocaleResolver: determines which locale is active for the current user.
Setting up localization in a Spring app
1. Add the dependencies
First, make sure you have the web and validation dependencies in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. Add language files
Create the following files under src/main/resources:
messages_en.properties:field.required=Field is required field.length=Field must not exceed {0} charactersmessages_ru.properties:field.required=Field is required field.length=Field must not exceed {0} characters
These files contain keys and translations. Keys are unique identifiers for messages, and values are the texts in the corresponding language.
3. Configure MessageSource
In Spring Boot, MessageSource is configured via a Bean in a configuration class. Add the following code:
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
@Configuration
public class LocalizationConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
This tells Spring to look for messages_*.properties files in the resources folder.
4. Configure LocaleResolver
Add a LocaleResolver to determine the user's language. For example, you can use a cookie to store the language choice:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import java.util.Locale;
@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH); // Default language
return localeResolver;
}
}
5. Switching locale via a controller
Now let's create a controller so users can change the language, for example via the lang parameter:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
@RestController
public class LanguageController {
private final LocaleResolver localeResolver;
public LanguageController(LocaleResolver localeResolver) {
this.localeResolver = localeResolver;
}
@GetMapping("/change-language")
public String changeLanguage(@RequestParam String lang, HttpServletRequest request, HttpServletResponse response) {
Locale locale = new Locale(lang);
localeResolver.setLocale(request, response, locale);
return "Language changed to " + lang;
}
}
Requests like /change-language?lang=ru will now switch the language.
Localizing validation error messages
Let's localize messages coming from the Bean Validation API.
1. Annotate DTO fields
Our UserDTO might look like this:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDTO {
@NotNull(message = "{field.required}")
private String username;
@Size(max = 20, message = "{field.length}")
private String password;
// Getters and setters
}
The validation message keys (field.required and field.length) are taken from the language files.
2. Example handling in a controller
Create a REST controller that handles a POST request for registration.
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@RequestBody @Valid UserDTO userDTO) {
return "User created: " + userDTO.getUsername();
}
}
Spring automatically validates requests and throws a MethodArgumentNotValidException on validation errors.
Localizing errors via GlobalExceptionHandler
Previous lectures taught us to use @ControllerAdvice for global error handling. Let's localize errors for users:
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
private final MessageSource messageSource;
public GlobalExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex, Locale locale) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
String localizedErrorMessage = messageSource.getMessage(error, locale);
errors.put(error.getField(), localizedErrorMessage);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
}
Now error messages will be returned according to the user's current locale!
Example request
Request body
{
"username": null,
"password": "password_that_is_too_long_for_the_application_rules"
}
Response for locale en
{
"username": "Field is required",
"password": "Field must not exceed 20 characters"
}
Response for locale ru
{
"username": "Field is required",
"password": "Field must not exceed 20 characters"
}
Now your Spring app speaks multiple languages. That looks more professional and makes the system friendlier to users! You officially finished localization.
GO TO FULL VERSION