79533852

Date: 2025-03-25 13:44:38
Score: 1.5
Natty:
Report link

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.

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.

Reasons:
  • RegEx Blacklisted phrase (2.5): Please share your
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • High reputation (-1):
Posted by: nickolay