Close

Spring - Internationalization in Spring

[Last Updated: Dec 22, 2023]

Spring supports internationalization (I18n) and localization (L10n)In this tutorial we will focuses on how to display the text labels/messages in different languages based on the provided locale.


An application written in Java, and Spring as well, are capable to support different languages by providing textual messages externally. These messages are usually written in .properties files (a Java standard).



The interface MessageSource

The main Spring interface to support of I18n/L10n messages is MessageSource

Definition of MessageSource

(Version: spring-framework 6.1.2)
package org.springframework.context;
   ........
public interface MessageSource {
    @Nullable
    String getMessage(
            String code,
            @Nullable Object[] args,
            @Nullable String defaultMessage,
            Locale locale); 
    String getMessage(String code, @Nullable Object[] args, Locale locale)
            throws NoSuchMessageException; 
    String getMessage(MessageSourceResolvable resolvable, Locale locale)
            throws NoSuchMessageException; 
}

where:

  • code is the a key which maps to the messages written in the external files (usually .properties files).
  • args are the runtime values to be substituted for the index based place-holders like {0} or {1} so on.

    Here's how a message in a properties file looks like:


  • locale is the target java.util.Locale instance.
  • resolvable: MessageSourceResolvable is an interface suitable for message resolution dynamically. Spring core validation error classes (FieldError, ObjectError) implement this interface. For simple use we can use the its implementation: DefaultMessageSourceResolvable.


MessageSource Implementation

Spring provides two out-of-the-box implementations:

  • ResourceBundleMessageSource: this internally uses java.util.ResourceBundle.
  • ReloadableResourceBundleMessageSource: It's able to reload messages from the source files based on timestamp changes, without restarting the application. It doesn't use ResourceBundle but uses it's own message loading and resolving logic. It can also detect XML property files and load them.





Also ApplicationContext interface extends MessageResource but it's implementation uses the underlying configured MessageResource as delegate, so it shouldn't be considered a full-blown implementation as above the two.

Naming convention for resource files?

The following file name format must be used for MessageResource to work (this is actually java.util.ResourceBundle standard):

basename_languageCode_countryCode.properties

'basename' can be related to the application part where these properties belong to. For example orderView_en_us.properties.

We can create as many message properties files as we want and they can be anywhere in the classpath.

Per good practices, the file groups should be divided under different folders based on different modules of the application.

Following project structure shows how to do a maintainable grouping in a maven project (resources folder is in classpath by default):

+--src
`--+--main
   |--+--java
      |     .........
   `--+--resources
      `--+--messages
         |--+--trade-module
         |  `--+--tradeDetails_en_us.properties
         |     `--tradeDetails_fr_FR.properties
         |        .........
         `--+--order-module
            `--+--orderHistory_en_us.properties
               |--orderHistory_fr_FR.properties
               |--orderPlacement_en_us.properties
               `--orderPlacement_fr_FR.properties
               |  .........

`

How to use MessageSource?

Spring doesn't provide any annotations based approach for message resolutions. Although, Spring does so for general purpose resource loading via Resource/@Value combination and @PropertySource/@Value combination for general purpose properties loading.

The reason of why there's no annotations support for resource messages is: the values resolved via annotations are static, which are loaded only at start up time and remain the same after that, whereas, Locale, in most practical scenarios, changes depending on different workflow. For example in Spring MVC application, a different locale is used for each different HTTP request client (with the help of 'Accept-Language' header) to generate the content in a different language which the client can understand.

The only appropriate way is to inject the implementation of MessageSource as a bean.

ResourceBundleMessageSource example

src/main/resources/messages/msg_en_us.properties

app.name = resource bundle test invoked by {0}
package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

import java.io.IOException;
import java.util.Locale;

public class MessageSourceExample {
    public static void main (String[] args) throws IOException {
        Locale.setDefault(Locale.US);
        //uncomment next line to change the locale
        //Locale.setDefault(Locale.FRANCE);
        AnnotationConfigApplicationContext context =
                            new AnnotationConfigApplicationContext(Config.class);

        MyBean bean = context.getBean(MyBean.class);
        bean.doSomething();
    }

    @Configuration
    public static class Config {

        @Bean
        public MyBean myBean () {
            return new MyBean();
        }

        @Bean
        public MessageSource messageSource () {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
            messageSource.setBasename("messages/msg");
            return messageSource;
        }
    }

    public static class MyBean {
        @Autowired
        private MessageSource messageSource;

        public void doSomething () {
            System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
                                                        Locale.getDefault()));
        }
    }
}

Output

resource bundle test invoked by Joe

Using MessageSourceAware

Instead of autowiring we can implement MessageSourceAware interface to get notified of the MessageSource.

package com.logicbig.example;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

import java.io.IOException;
import java.util.Locale;

public class MessageSourceAwareExample {
    public static void main (String[] args) throws IOException {
        Locale.setDefault(Locale.US);
        //uncomment next line to change the locale
        //   Locale.setDefault(Locale.FRANCE);
        AnnotationConfigApplicationContext context =
                            new AnnotationConfigApplicationContext(Config.class);
        MyBean bean = context.getBean(MyBean.class);
        bean.doSomething();
    }


    @Configuration
    public static class Config {

        @Bean
        public MyBean myBean () {
            return new MyBean();
        }

        @Bean
        public MessageSource messageSource () {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
            messageSource.setBasename("messages/msg");
            return messageSource;
        }
    }

    public static class MyBean implements MessageSourceAware {
        private MessageSource messageSource;

        public void doSomething () {
            System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
                                                        Locale.getDefault()));
        }

        @Override
        public void setMessageSource (MessageSource messageSource) {
            this.messageSource = messageSource;

        }
    }
}

Output

resource bundle test invoked by Joe

ReloadableResourceBundleMessageSource example

 @Configuration
 public class Config {

        @Bean
        public MyBean myBean () {
            return new MyBean();
        }

        @Bean
        public MessageSource messageSource () {
            ReloadableResourceBundleMessageSource messageSource =
                                            new ReloadableResourceBundleMessageSource();
            messageSource.setBasename("classpath:messages/msg");
            messageSource.setDefaultEncoding("UTF-8");
            //refresh cache after every 500 mill-secs
            messageSource.setCacheMillis(500);
            return messageSource;
        }
 }

The following code access the message 20 times and sleeps for 2 sec every time. So we have enough time to change the messages in the files to see the effect in real time. Also remember to change the correct properties file which are under the target/classes folder (maven specific build folder) because those are the ones which are loaded during runtime.

public static class MyBean {
     @Autowired
     private MessageSource messageSource;

     public void doSomething () {
            for (int i = 0; i < 20; i++) {
                System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
                                                            Locale.getDefault()));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


Working with multiple message sources

If we want to use multiple sources with different base names, we have two choices:

  • Set multiple sources using setBasenames(String... basenames):

    For example:

            @Bean
            public MessageSource messageSource () {
                ResourceBundleMessageSource messageSource =
                                              new ResourceBundleMessageSource();
                messageSource.setBasenames("messages/msg", "messages/msg2");
                return messageSource;
            }

    The message codes are searched in order the files are added in above method and first match will be returned.


  • Set sources in hierarchical order using setParentMessageSource(parentMessageSource):

    For example:

            @Bean
            public MessageSource messageSource () {
                //child
                ResourceBundleMessageSource messageSource =
                                                new ResourceBundleMessageSource();
                messageSource.setBasename("messages/msg2");
               //parent
                ResourceBundleMessageSource parentMessageSource =
                                                new ResourceBundleMessageSource();
                parentMessageSource.setBasename("messages/msg");
    
                messageSource.setParentMessageSource(parentMessageSource);
                return messageSource;
            }

    This is useful when we want to override some of the messages from parent. The child MessageSource is searched for the message code first, if message is not found then parent is searched.




Example Project

Dependencies and Technologies Used:

  • spring-context 6.1.2 (Spring Context)
     Version Compatibility: 4.3.0.RELEASE - 6.1.2Version List
    ×

    Version compatibilities of spring-context with this example:

    • 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
    • 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
    • Compatible Java Version: 17+
    • 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.1.0
    • 6.1.1
    • 6.1.2

    Versions in green have been tested.

  • JDK 17
  • Maven 3.8.1

Message Sources Examples Select All Download
  • spring-core-i18n
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • MessageSourceExample.java
          • resources
            • messages
            • messages-xml

    See Also