A Java open source memoization library to cache the results of slow methods using annotations.
I initially created this library back in 2007 using Java 5 and Ant build, this was before Github existed. I hosted the code on my own site, but it was a headache to keep it up-to-date, so by 2009, I stopped updating it. However, I always intended to update it and publish it on Github, so here it is again.
- Here is the old code before migrating to github
- Here is the old programmer's introduction
The remainder of this document describes the new and updated version.
If a function produces the same output given the same inputs, and if this function is slow, it makes sense for the function to cache its outputs to avoid repeated evaluations. This caching behavior is called Memoization (yes, there is no r).
This library provides a simple mechanism to memoize any Java method using @Remember annotation.
Suppose that you have a method which reads some authorization string from database for a given login:
// This is not ideal code, it is meant only to show a concept
public String readAuthorizationFromDb(String loginId) {
// Never do this if loginId is coming from user input!!
String sql = "select authorization from security where login_id='" + loginId + "'";
try (Connection con = getDbConnection()) {
return readFromDb(con, sql);
} catch (Exception e) {
e.printStackTrace();
}
}If this function is called many times, with the same loginId, a programmer may consider this kind of optimization:
Map<String, String> cache= new HashMap<>(); // <<<
public String readAuthorizationFromDb(String loginId) {
if (cache.containsKey(loginId)) { // <<<
return cache.get(loginId); // <<<
} // <<<
// Never do this if loginId is coming from user input!!
String sql = "select authorization from security where login_id='" + loginId + "'";
try (Connection con = getDbConnection()) {
String auth = readFromDb(con, sql);
cache.put(loginId, auth); // <<<
return auth;
} catch (Exception e) {
e.printStackTrace();
}
}This solution could work for a simple program, however it suffers from several problems such as:
- There is no control on the size of the map. If the method is called many times with different loginIds, the size of map can increase and consume a lot of memory.
- There is no control on expiring items in the cache. Once a loginId is put in the map it stays there, if the database contents change, there is no way to update the content of the map besides restarting the program.
- Having cache handling code in every method that needs caching is annoying and error prone.
Other solutions include:
- Using an Aspects-oriented programming library like AspectJ
- Using Spring framework
@Cacheableannotation
Both AspectJ and Spring are highly used and safe options.
This library however, is a simple and specialized solution for memoization in Java.
Add the following dependency to your maven's pom.xml:
<dependency>
<groupId>com.tek271</groupId>
<artifactId>memoize</artifactId>
<version>2.0.1</version>
</dependency>You can also directly download the source form https://github.com/ahabra/memoize and use it. Note that this library uses Byte Buddy for byte code instrumentation.
Let's see how the same readAuthorizationFromDb() will look with the Tek271 Memoizer:
import com.tek271.memoize.Remember;
class AuthDao {
@Remember
public String readAuthorizationFromDb(String loginId) {
// Never do this if loginId is coming from user input!!
String sql = "select authorization from security where login_id='" + loginId + "'";
try (Connection con = getDbConnection()) {
return readFromDb(con, sql);
} catch (Exception e) {
e.printStackTrace();
}
}
}As you can see, the only needed addition is the @Remember annotation before the method's declaration.
Now, calling readAuthorizationFromDb() with the same loginId will not cause a DB read for each
call.
To invoke the readAuthorizationFromDb() method:
import com.tek271.memoize.RememberFactory;
// Create an instance of AuthDao
AuthDao authDao = RememberFactory.createProxy(AuthDao.class);
String auth = authDao.readAuthorizationFromDb('some-user-login');This library provides two static methods and two annotation, which will be discussed next.
It is defined as:
package com.tek271.memoize;
public class RememberFactory {
public static <T> T createProxy(Class<T> targetClass) {
//...
}
}Creates a caching (memoizing) proxy instance for a class that contains methods with @Remember annotation.
Calling these methods will cause them to be cached.
The targetClass must provide a parameter-less constructor.
It is defined as:
package com.tek271.memoize;
public class RememberFactory {
public static <T> T decorate(T objectToDecorate) {
//...
}
}Creates a caching (memoizing) decorator for an existing object. The object's class definition
should contain methods with @Remember annotation.
Usually, you will call the decorate() method when you have an object already created by some
other library (e.g. Spring).
You can apply the @Remember annotation on methods that are:
- Not final
- Not static
- Not void
- For the same parameters values, the method must always return the same value
- The method must not have any side effects like setting fields or properties
- The method's parameters must support correct
equals()andhashCode()methods
The @Remember annotation provides the following optional parameters:
maxSize: int. Default value = 128. The maximum size of cache for the given method.timeToLive: long. Default value = 2. The period of time after which, cached return values of the method will expire.timeUnit: TimeUnit. Default value = TimeUnit.MINUTES
For example, if we need to memoize a method and cache up to 1000 values for up to one hour:
@Remember(maxSize=1000, timeToLive=1, timeUnit=TimeUnit.HOURS)Sometimes, not all method parameters should be used as a part of the cache's key.
You can apply the @Exclude annotation on these parameters. For example:
import com.tek271.memoize.Exclude;
import com.tek271.memoize.Remember;
@Remember
String readUserAddress(@Exclude Connection con,
String userName) {
// ...
}The Connection object is not something that you should include in a cache's key.
- Byte buddy: https://bytebuddy.net
- Using Byte Buddy for proxy creation: https://www.javacodegeeks.com/2022/02/using-byte-buddy-for-proxy-creation.html
- Create Proxies Dynamically Using CGLIB Library: https://objectcomputing.com/resources/publications/sett/november-2005-create-proxies-dynamically-using-cglib-library
- Version 1.0, 2007.03.01. First public release
- Version 1.01, 2007.03.05. Some JavaDocs fixes and spelling mistakes.
- Version 1.1, 2009.06.27
- Updated dependency to latest cglib jar (version 2.2)
- Added RememberFactory.clearCache() methods to ease unit testing.
- Added an optimization proposed by Christian Semrau.
- Added Generics support to RememberFactory.createProxy()
- Added RememberFactory.decorate() to decorate a given object (rather than class). This feature was requested by Patrick McMichael.
- Version 2.0.0, 2025.11.18
- Total re-write
- Migrate from cglib to Byte Buddy
- Use Maven build
- Added @Exclude annotation
- Publish on maven central
- Version 2.0.1, small typo fixes