MessageSource in Custom Spring Boot Starter

I help maintain a number of applications at work. One common feature among them is exposing a diagnostics webpage. I didn’t want to reinvent the wheel, so I decided to make the entire feature a Spring Boot starter. I didn’t want to hardcode all the text directly into the HTML document, however. Instead I wanted to utilize Spring’s MessageSource to fetch all the text from a .properties-file, but this caused a few headaches.

First of all, Spring Boot’s default MessageSource uses the property spring.messages.basename to decide which file(s) to read from. I couldn’t set this in my starter since users of my starter might need it for their own application. Furthermore, I couldn’t replace the MessageSource with my own like Sodik suggests, as it would reinitialize the application context. They suggest a proxy class that delegates lookups to the original MessageSource unless the message key is special. The idea proved to be a complicated one because the MessageSource interface’s methods are made final by AbstractMessageSource.

A possible, yet undesirable workaround was to make users copy the messages that the webpage required into their own messages.properties-file. I wanted to save them from that manual step, however; what good is auto-configuration if it doesn’t configure things automatically?

Giving MessageSource a Parent

I discovered that ResourceBundleMessageSource, which is the type that Spring Boot creates by default, can have a parent. It uses its parent to search for a message when it’s unable to find it on its own.

I made a BeanPostProcessor bean that sets a new ResourceBundleMessageSource as the original MessageSource’s parent. I also configured the new ResourceBundleMessageSource with my own basename. The code listing below shows the bean definition.

@Bean
BeanPostProcessor messageSourceCustomExtender() {
    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof HierarchicalMessageSource && beanName.equals("messageSource")) {
                ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
                parent.setBasename("custom");

                ((HierarchicalMessageSource) bean).setParentMessageSource(parent);
            }

            return bean;
        }
    };
}

Caveats

The code above assumes that there’s a HierarchicalMessageSource bean by the name “messageSource” on the context. However, there could be an entirely different type of MessageSource bean on the context. Such a bean prevents the MessageSource auto-configuration from doing its job, which in turn effectively disables this BeanPostProcessor. When using this approach, I recommend giving some thought to how the code can be made more robust.

Another thing to bear in mind is that the MessageSource bean whose parent we’re setting might have a parent of its own. You don’t want to overwrite any existing parents, so make sure you traverese the heritage tree first. Only an orphan should have its parent set.

Links and Resources

Below are a few links that helped me research this topic:

I’ve made a sample project on GitHub that showcases a barebones proof of concept for the solution I arrived at. I used Maven and Spring Boot 1.4.0 and added unit tests to it. I haven’t used any Java 8-specific features, so you can easily convert it to Java 8.

Thomas Kåsene

Thomas Kåsene

Thomas is a certified Java professional and focuses on writing clean code that is easy to read, unit test and maintain. In addition to having worked on large enterprise applications, he's also developed several Android apps and websites through the years. In 2011, Thomas graduated with a Bachelor of Science in IT from the then Norwegian School of Information Technology.

More Posts - Website

Follow Me:
TwitterLinkedIn