Spring Boot: Conditional Caching in 6 lines of code.
Tags: java, spring, rest, web
Published: 2020-11-22 18:07:02

---
title: Spring Boot: Conditional Caching in 6 lines of code.
published: true
description: Add conditional caching to your Spring Boot application in just 3 lines of code.
tags: java, spring, rest, web
cover_image: https://i.redd.it/0ijqk93ga2061.png
---

The Spring Boot framework includes a simple, server-side caching solution which is easy to enable and see immediate results and even easier to extend using robust caching solutions all through an Annotation based interface that makes it all feel like configuration rather than implementation.

When you work with different sources of data, your cache control rules may vary and conditions may need to be evaluated before performing caching operations. With this requirement, we risk polluting our business code with caching logic. Spring Boot's [`@Cacheable`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html) interface handles this elegantly by employing Java Annotations and the [Spring Expression Language (SpEL)](https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html).

# Use Case

The Demo application serves as Backend-For-Frontend service for Typicode's [JSONPlaceholder](https://jsonplaceholder.typicode.com/) which serves RESTful JSON samples which can then be used for demos and tests concerning external APIs.

For the sake of this tutorial, the JSONPlaceholder API has a significant network latency of 3 seconds in every call to read collections and records. We could cache every single endpoint but the stakeholders have stated an additional requirement. We can only cache the `/users/`, `/albums` and `/photos` endpoints. All the others endpoints contain data that cannot be cached for reasons. The Front-End team will implement a loading screen for those endpoints instead.

# Starter Template

To setup the demo, clone the [Github repo](https://github.com/LySofDev/java-spring-conditional-caching-demo.git) and checkout the 1.0 tag as a new branch.

```sh
$ git clone https://github.com/LySofDev/java-spring-conditional-caching-demo.git .

$ cd java-spring-conditional-caching-demo

$ git checkout tags/1.0 -b demo
```

# Implementation Details

The `PlaceholderController` will pass along the arguments from the request URL and the application configuration to the `JSONPlaceholder` service which will perform the REST call to the JSONPlaceholder API.

`src/main/java/com/example/conditionalcachingdemo/controllers/PlaceholderController.java`

```java
@GetMapping("/{name}")
public JSONArray getCollection(@PathVariable String name) {
    return placeholderService.getCollection(name);
}

@GetMapping("/{name}/{id}")
public JSONObject getRecord(@PathVariable String name, @PathVariable int id) {
    return placeholderService.getRecord(name, id);
}
```

`src/main/java/com/example/conditionalcachingdemo/services/PlaceholderServiceImpl.java`

```java
@Override
public JSONArray getCollection(String name) {
    try {
        JSONArray collection = restTemplate.getForObject(collectionUrl(name), JSONArray.class);
        Thread.sleep(3000); // Simulating a slow network.
        return collection;
    } catch (HttpClientErrorException.NotFound | InterruptedException e) {
        throw new CollectionNotFoundException();
    }
}

@Override
public JSONObject getRecord(String name, int id) {
    try {
        JSONObject record = restTemplate.getForObject(recordUrl(name, id), JSONObject.class);
        Thread.sleep(3000); // Simulating a slow network.
        return record;
    } catch (HttpClientErrorException.NotFound | InterruptedException e) {
        throw new RecordNotFoundException();
    }
}
```

These two code excerpts cover the meat of the application. Everything else in the repository is built to provide this functionality to the end user. Feel free to take a moment to look at the rest of the repo as most files are brief.

To simulate the JSONPlaceholder API's network latency, we'll include a 3 second wait period in our code after fetching data from the API.

# Enabling the Cache

`src/main/java/com/example/conditionalcachingdemo/ConditionalCachingDemoApplication.java`

```java
@SpringBootApplication
@EnableCaching
public class ConditionalCachingDemoApplication {
```

The implementation of the cache is based on the `CacheManager` interface. The Spring framework includes a simple implementation which stores cache data in memory. This is fine for development but, in production, we'll want to use an external caching solution like Redis or Cassandra. Replacing the default implementation is as simple as providing an alternative implementation of the `CacheManager` interface.

# Cache Configuration

We'll update the application configuration to include a list of the cacheable endpoints.

`src/main/resources/application.properties`

```java
api.url=https://jsonplaceholder.typicode.com/
api.cacheables=users,albums,photos
```
Then, we'll add the `@Cacheable` annotations to the `PlaceholderController` and update the method signatures to include the `api.cacheables` configuration values.

`src/main/java/com/example/conditionalcachingdemo/controllers/PlaceholderController.java`

```java
@GetMapping("/{name}")
@Cacheable(value = "collections", condition = "#cacheables.contains(#name)")
public JSONArray getCollection(@PathVariable String name, @Value("${api.cacheables}") List<String> cacheables) {
    return placeholderService.getCollection(name);
}

@GetMapping("/{name}/{id}")
@Cacheable(value = "records", key = "#name + #id", condition = "#cacheables.contains(#name)")
public JSONObject getRecord(@PathVariable String name, @PathVariable int id, @Value("${api.cacheables}") List<String> cacheables) {
    return placeholderService.getRecord(name, id);
}
```

That's it. Run the Spring Boot application and hit the cacheable endpoints. The first time we hit an endpoint, it will take 3 seconds to respond since the cache hasn't yet been hydrated. Once the data from JSONPlaceholder is loaded into the cache, subsequent requests to the endpoint will be served almost immediately since the data is coming from memory and not the network.

# Solution Analysis

The `@Cacheable` annotation includes two arguments in the `getCollection` method. The `value` argument indicates the cache name for the collection. This allows us to set different configurations per cache such as expiry times, etc. The `condition` argument validates that the `name` of the endpoint provided in the URL is included in the `api.cacheables` configuration.

You might be wondering why we include the `cacheables` value in the controller method signature instead of making it part of the class itself. The SpEL only interacts with the variables that are declared static but the `@Value` annotation can only be applied on an instance variable or a method parameter. Therefore, to provide the configuration values `api.cacheables` to the `@Cacheable` `condition`, we'll include them as part of the method signature `List<String> cacheables` and let the `@Value` annotation fill the values in during runtime.

In the `getRecord` method, we also use the `key` argument to add a mapping expression `#name + #id` to generate the key for this record in cache from the collection name and record id. In the `getCollections` method, the key will default to the value of the `name` variable.

# Conclusion

The Spring Boot application now satisfies the caching requirement and can be pushed to a UAT environment for demonstration to the stakeholders while the underlying caching solution for the production environment is developed.