Dynamic message sources with pattern in Spring Boot

Jul 11, 2021
spring boot java

The number of resource bundles increases when developing multi-language APIs in Spring. Adding a new resource bundle to the Bean each time can be overwhelming.

Although ReloadableResourceBundleMessageSource.setBasenames does not support patterns, the problem can be solved using org.springframework.core.io.support.PathMatchingResourcePatternResolver.

@Bean
public MessageSource messageSource() throws IOException {
  ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
    getClass().getClassLoader()
  );
  // Find all resource files in src/main/resources/messages folder.
  Resource[] resources = resolver.getResources("classpath:messages/**/*.properties");

  Path classpath = Path.of(
    org.apache.commons.lang3.StringUtils.substringBefore(System.getProperty("java.class.path"), ":")
  );

  var basenames = new HashSet<String>();

  for (var resource : resources) {
    String basename;
    if (resource instanceof FileSystemResource) { // The executable is class file (target/classes etc).
      // Extract class path to find resource name.
      basename = classpath.relativize(resource.getFile().toPath()).toString();
    } else if (resource instanceof ClassPathResource) { // The process is jar-like single file.
      basename = ((ClassPathResource) resource).getPath();
    } else {
      throw new IllegalStateException("Resource must be either FileSystemResource or ClassPathResource.");
    }

    // Remove file extension and language code from file name.
    String resourceName = basename.replaceAll("(|_[a-z]{2}_[A-Z]{2}).properties$", "");

    basenames.add("classpath:" + resourceName);
  }

  // Load collected basenames.
  messageSource.setBasenames(basenames.toArray(String[]::new));
  // Configure message source...
  messageSource.setDefaultEncoding("UTF-8");

  return messageSource;
}