1. Overview
Letβs look at an example of using Ehcache with Spring Boot. Weβll use Ehcache version 3 as this provides an implementation of a JSR-107 cache manager.
The example is a simple REST service that produces the square of a number.
2. Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>3.1.5</version></dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
<classifier>jakarta</classifier>
</dependency>
3. Example
Letβs create a simple REST controller that calls a service to square a number and returns the result as a JSON string:
@RestController
@RequestMapping("/number", MediaType.APPLICATION_JSON_UTF8_VALUE)
public class NumberController {
// ...
@Autowired
private NumberService numberService;
@GetMapping(path = "/square/{number}")
public String getSquare(@PathVariable Long number) {
log.info("call numberService to square {}", number);
return String.format("{\"square\": %s}", numberService.square(number));
}
}
Now letβs create the service.
We annotate the method with @Cacheable so that Spring will handle the caching. As a result of this annotation, Spring will create a proxy of the NumberService to intercept calls to the square method and call Ehcache.
We need to provide the name of the cache to use and optionally the key. We can also add a condition to restrict what is cached:
@Service
public class NumberService {
// ...
@Cacheable(
value = "squareCache",
key = "#number",
condition = "#number>10")
public BigDecimal square(Long number) {
BigDecimal square = BigDecimal.valueOf(number)
.multiply(BigDecimal.valueOf(number));
log.info("square of {} is {}", number, square);
return square;
}
}
Finally, letβs create our main Spring Boot application:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. Cache Configuration
We need to add Springβs @EnableCaching annotation to a Spring bean so that Springβs annotation-driven cache management is enabled.
Letβs create a CacheConfig class:
@Configuration
@EnableCaching
public class CacheConfig {
}
Springβs auto-configuration finds Ehcacheβs implementation of JSR-107. However, no caches are created by default.
Because neither Spring nor Ehcache looks for a default ehcache.xml file. We add the following property to tell Spring where to find it:
spring.cache.jcache.config=classpath:ehcache.xml
Letβs create an ehcache.xml file with a cache called squareCache:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<cache alias="squareCache">
<key-type>java.lang.Long</key-type>
<value-type>java.math.BigDecimal</value-type>
<expiry>
<ttl unit="seconds">30</ttl>
</expiry>
<listeners>
<listener>
<class>com.baeldung.cachetest.config.CacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache>
</config>
And letβs also add the cache event listener which logs both CREATED and EXPIRED cache events:
public class CacheEventLogger
implements CacheEventListener<Object, Object> {
// ...
@Override
public void onEvent(
CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.info(/* message */,
cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
}
}
5. In Action
We can use Maven to start this app by running mvn spring-boot:run.
Then open up a browser and access the REST service on port 8080.
If we go to http://localhost:8080/number/square/12, then weβll get back {βsquareβ:144}, and in the log weβll see:
INFO [nio-8080-exec-1] c.b.cachetest.rest.NumberController : call numberService to square 12
INFO [nio-8080-exec-1] c.b.cachetest.service.NumberService : square of 12 is 144
INFO [e [_default_]-0] c.b.cachetest.config.CacheEventLogger : Cache event CREATED for item with key 12. Old value = null, New value = 144
We can see the log message from the square method of NumberService, and the CREATED event from the EventLogger. If we then refresh the browser we will only see the following added to the log:
INFO [nio-8080-exec-2] c.b.cachetest.rest.NumberController : call numberService to square 12
The log message in the square method of NumberService isnβt being invoked. This shows us that the cached value is being used.
If we wait 30 seconds for the cached item to expire and refresh the browser weβll see an EXPIRED event, and the value added back into the cache:
INFO [nio-8080-exec-1] (...) NumberController : call numberService to square 12
INFO [e [_default_]-1] (...) CacheEventLogger : Cache event EXPIRED for item with key 12. Old value = 144,New value = null
INFO [nio-8080-exec-1] (... )NumberService : square of 12 is 144
INFO [e [_default_]-1] (...) CacheEventLogger : Cache event CREATED for item with key 12. Old value = null, New value = 144
If we enter http://localhost:8080/number/square/3 into the browser, we get the correct answer of 9, but the value isnβt cached.
This is because of the condition we used on the @Cacheable annotation to only cache values for numbers higher than 10.
6. Conclusion
In this quick tutorial, we showed how to set up Ehcache with Spring Boot.
