1

I am writing a Java Spring Boot (servlet) web-API application. There are repository classes in the application. Repository methods are written "from scratch" using Google Cloud Datastore API looking like this:

    @Override
    public User save(User user) {
        try {
            // Datastore specific code to populate the user entity
            // to then save it into the database

            Entity entity = ...

            datastore.put(entity);
        } catch (DatastoreException datastoreException) {
            String errorMessage = messageSource.getMessage(
                "persistence.datastore.failed_to_save", 
                new Object[]{User.KIND}, // where User.KIND is "User" string
                LocaleContextHolder.getLocale());
            throw new DatastorePersistenceException(errorMessage, datastoreException);
        }
        return user;
    }

I wrap database communication code into try catch. When google.DatastoreException occurs I catch it and then create new project-local DatastorePersistenceException exception, populate it with error message and actual google's exception and then throw this new populatd DatastorePersistenceException further.

The problem

I want to avoid hardcoded string literals with the exception specific message. Instead I want to utilize messages.properties to get the exact message string by key: persistence.datastore.failed_to_save=Failed to save {} in Datastore. I also want to keep it short and use the same message key and pattern for all DatastorePersistenceExceptions so I am using string interpolation.

The problem is that string interpolation requires the following code that allocates memory in new Object[]{User.KIND}:

        } catch (DatastoreException datastoreException) {
            String errorMessage = messageSource.getMessage(
                "persistence.datastore.failed_to_save", 
                new Object[]{User.KIND}, 
                LocaleContextHolder.getLocale());
            throw new DatastorePersistenceException(errorMessage, datastoreException);
        }

where messageSource is private final MessageSource messageSource;.

Why I think this is a problem

  1. memory allocations should be avoided in catch blocks as they can lead to memory allocation failures.

What am I looking for

  1. avoid memory allocation in catch block and still utilize string interpolation.

P.S. At the same time there is new DatastorePersistenceException... thrown that involves memory allocation in catch block. But this is a working pattern if you want to have higher level of abstraction leaving database vendor's specific exception class incapsulated. Is new DatastorePersistenceException also a "risky" practice in the sense of memory allocation in catch block?

6
  • 2
    Frame challenge: allocations in a catch block only matter if you are catching an OutOfMemoryError and even then allocating a single one-element array is unlikely to matter. This seems like serious overthinking to me. Commented Mar 25 at 16:38
  • 1
    Memory allocation in the catch block is much better than any of the solutions you've described or any of the solutions that are realistically possible. Stick with your current code. Commented Mar 25 at 19:23
  • You are overthinking this. There is nothing wrong with creating a new exception in a catch block. Spring, HIbernate and other frameworks don't do anything else. The memory allocation is short lived it will be freed quite quickly. Commented Mar 25 at 20:46
  • @LouisWasserman thank you. Please show me points why the current code is better than the code in "solutions"? I am looking for being convinced by knowledge. Commented Mar 25 at 21:04
  • Where did you get the idea that memory allocations should be avoided in catch blocks? That's just not true. And given that, there's nothing wrong with your original code. Commented Mar 25 at 21:07

2 Answers 2

0

If you still want to eliminate the new Object[] allocation, you could predefine message arguments as constants:

privatestatic final Object[] USER_KIND_ARGS = new Object[] { User.KIND };

But this only works if User.KIND is constant and doesn't change per call.

Alternatively, you could lazily create the Object[] outside the catch block:

Object[] kindArgs = new Object[] { User.KIND }; try { ... } catch (DatastoreException ex) { String msg = messageSource.getMessage("persistence.datastore.failed_to_save", kindArgs, LocaleContextHolder.getLocale()); throw new DatastorePersistenceException(msg, ex); }

However, this just moves the allocation, it doesn’t eliminate it.

Sign up to request clarification or add additional context in comments.

1 Comment

"move allocation" is the correct term as my goal is to avoid allocations in catch block. I posted my current solution, that is imperfect but I stuck with it as I don't have better ideas yet.
0

Here is my current solution, that is imperfect but I stuck with it as I don't have better ideas yet.

Goal

I want to move memory allocation outside of the catch block, so the memory allocation does not happen in catch block.

Considered solutions

1. Allocate string interpolation Object[] array in each repository

Here is the code:

@Repository
public class UserRepositoryImpl implements UserRepository {

    private static final Object[] USER_ENTITY_ARG = new Object[]{User.KIND};
    
    private final Datastore datastore;
    private final KeyFactory keyFactory;
    private final MessageSource messageSource;
    
    // Constructor and other methods...
    
    @Override
    public User save(User user) {
        try {
            // Existing implementation...
            datastore.put(entity);
        } catch (DatastoreException datastoreException) {
            String errorMessage = messageSource.getMessage(
                    "persistence.datastore.failed_to_save",
                    USER_ENTITY_ARG,
                    LocaleContextHolder.getLocale());
            throw new DatastorePersistenceException(errorMessage, datastoreException);
        }
        return user;
    }
    
    // Other methods...
}

Pros

  1. It allocates memory at repository class loading. The goal of avoiding memory allocation in catch block achieved.

Cons

The code

private static final Object[] USER_ENTITY_ARG = new Object[]{User.KIND};
  1. looks like garbage in repository class breaking the single responsibility principle, adding clutter to code.
  2. adds burden for developer to follow this "convention" of allocating interpolation strings in each repository.

Verdict

I do not like it for its' cons.

2. MessageHelper class with encapsulated Spring's MessageSource and pre-allocated memory for Object[] string interpolation arrays

Here is the helper class:

@Service
public class MessageHelperImpl implements MessageHelper { // MessageHelper is just my interface you can see @Override's of its methods

    private static final ThreadLocal<Object[]> ARGS1 = ThreadLocal.withInitial(() -> new Object[1]);
    private static final ThreadLocal<Object[]> ARGS2 = ThreadLocal.withInitial(() -> new Object[2]);

    private final MessageSource messageSource;

    @Autowired
    public MessageHelperImpl(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    ... 

    @Override
    public String getMessage(String code, Object arg1) {
        Object[] args = ARGS1.get();
        args[0] = arg1;
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }

    @Override
    public String getMessage(String code, Object arg1, Object arg2) {
        Object[] args = ARGS2.get();
        args[0] = arg1;
        args[1] = arg2;
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }

    @Override
    public String getMessage(String code, Object... args) {
        // For varargs, we use the provided array
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }

}

then the catch block looks like this:

        } catch (DatastoreException datastoreException) {
            // Using the specialized single-argument method
            String errorMessage = messageHelper.getMessage(
                    "persistence.datastore.failed_to_save", User.KIND); // User.KIND is just a String containing "User"
            throw new DatastorePersistenceException(errorMessage, datastoreException);
        }

Pros

  1. encapsulation of message retrieval from messages.properties and string interpolation logic
  2. possibly ThreadLocal ARGS1 and ARGS2 buffers will be allocated before the actual call from the exception. So it does not solve my concern.

Cons

  1. While ThreadLocal fields itself are created when the MessageHelperImpl class is loaded - Object[] arrays created lazily.
    • First time a thread calls ARGS1.get() that call can be right from the exception
    • Created by the supplier () -> new Object[1]
    • Each thread gets its own array instance
    • The array is reused for all subsequent calls from the same thread.

Verdict

I do not like this solution as it does not guarantee to solve my concern - not to allocate new memory in catch block.

I see that potentially I can call the ARGS1.get() for each thread on initialization of the thread but it looks sooo messy like a very poor workaround.

3. Servlet filter that forces MessageHelper initialization on per-thread basis

For every HTTP request Tomcat allocates a thread from its ThreadPool. So we can utilize Filter Chain for per-thread guaranteed initialization of ThreadLocals. Here is the code that will run on every HTTP request:

@Component
public class ThreadLocalInitializerFilter implements Filter {

    private final MessageHelper messageHelper;

    @Autowired
    public ThreadLocalInitializerFilter(MessageHelper messageHelper) {
        this.messageHelper = messageHelper;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        // Access messageHelper to initialize ThreadLocal arrays for this thread
        messageHelper.getMessage("dummy.key");
        
        chain.doFilter(request, response);
    }
}

Pros

  1. Reaches the goal (probably. See cons).

Cons

  1. I am not sure if this same thread will always process the repository code. Please suggest.
  2. Looks over-engineered - a special filter for two small arrays!

Verdict

I am not happy with this one either.

Conclusion

At this moment I do not have working solution that is architecturally nice for the problem of having string interpolation for exception messages.

Please share your thoughts.

P.S. And I am still wondered whether this is normal to create a new exception in the catch block - while it is an existing practice of abstracting your app's exception processing from the "vendor-specific" exceptions it still allocates memory in catch block what is considered a bad practice.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.