Skip to main content

Caching

Caching is disabled by default.

A basic cache is provided for simple usages. Any other cache can be plugged in by implementing the MessageFormatLoadingCacheProvider interface, see Guava Cache example.

Be aware of thread-safety when using caching.

Why use a cache?​

MessageFormats can be costly to instantiate. When performance matters, if the same message is going to be formatted over and over it is better to only instantiate it once.

Avoid​

class Example {

{
for (int i = 0; i < 5; i++) {
System.out.println(
new MessageFormat("Welcome {username}!").format(ImmutableMap.of("username", "Bob")));
}
}
}

Prefer​

Instantiate the MessageFormat outside of the loop and keep re-using it.

class Example {

{
MessageFormat messageFormat = new MessageFormat("Welcome {username}!");
for (int i = 0; i < 5; i++) {
System.out.println(messageFormat.format(ImmutableMap.of("username", "Bob")));
}
}
}

Multiple messages / different scopes​

Maintaining instances for multiple messages and scope can become tedious. L10nMessages will solve this with cache enabled.

class Example {

{
ConcurrentHashMap<String, MessageFormat> messagesConcurrentHashMap = new ConcurrentHashMap<>();
messagesConcurrentHashMap.put("welcome_user", new MessageFormat("Welcome {username}!"));
messagesConcurrentHashMap.put("bye_user", new MessageFormat("Bye {username}!"));

scope1:
{
for (int i = 0; i < 5; i++) {
System.out.println(
messagesConcurrentHashMap.get("welcome_user")
.format(ImmutableMap.of("username", "Bob")));
}
}

scope2:
{
for (int i = 0; i < 5; i++) {
System.out.println(
messagesConcurrentHashMap.get("welcome_user")
.format(ImmutableMap.of("username", "Bob")));
System.out.println(
messagesConcurrentHashMap.get("bye_user").format(ImmutableMap.of("username", "Bob")));
}
}
}
}

Similarly with disabled cache​

Following code will re-create the MessageFormat in each iteration

class Example {

{
L10nMessages<Messages> m = L10nMessages.builder(Messages.class).build();

for (int i = 0; i < 5; i++) {
System.out.println(m.format(welcome_user, "username", "Bob"));
}
}
}

With cache enabled​

The MessageFormats will be instantiated only once, and accessing them is transparent.

class Example {

{
L10nMessages<Messages> m =
L10nMessages.builder(Messages.class)
.messageFormatLoadingCacheProvider(CONCURRENT_HASH_MAP)
.build();

scope1:
{
for (int i = 0; i < 5; i++) {
System.out.println(m.format(welcome_user, "username", "Bob"));
}
}

scope2:
{
for (int i = 0; i < 5; i++) {
System.out.println(m.format(welcome_user, "username", "Bob"));
System.out.println(m.format(bye_user, "username", "Bob"));
}
}
}
}

Basic Cache​

A basic cache based on a ConcurrentHashMap is available.

class Example {

{
L10nMessages<Messages> m =
L10nMessages.builder(Messages.class)
.messageFormatLoadingCacheProvider(CONCURRENT_HASH_MAP)
.build();
}
}

Guava Cache​

This is an example how to implement the MessageFormatLoadingCacheProvider interface with a Guava Cache. For the cache configuration itself, check more information at Guava Caches Explained

class Example {

{
L10nMessages<Messages> m =
L10nMessages.builder(Messages.class)
.messageFormatLoadingCacheProvider(
(locale, messageFormatProvider) ->
CacheBuilder.newBuilder()
.maximumSize(10000)
.build(
CacheLoader.from(
(String message) -> messageFormatProvider.get(message, locale)))
::getUnchecked)
.build();
}
}

Thread Safety​

When using a cache, it is important to understand the thread-safety of the underlying MessageFormat before sharing the L10nMessages instance between multiple threads.

Consider having 1 instance of L10nMessages per thread, or to have some sort of synchronization around the MessageFormat instance.

From the ICU4J documentation:

MessageFormats are not synchronized.

It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.