From Hell To Heaven: Increase app’s performance with Redis

Welcome to my new series “From Hell To Heaven“. These series will be about different cases of programmers dealing with issues that anyone can find in a typical day. In this first post, we will be implementing Redis as a second level cache layer using Spring Boot.


I have been working on a couple of projects and encountered several performance issues. This issue relied mostly on reading the same data multiple times in the same endpoint. Using second layer cache is a common solution to this issue and that’s what we are going to do today! You will see how easy it will be to integrate Redis as a cache layer using Spring boot.

What is Redis?

Redis is an in-memory data structure used as a database or cache. Being in-memory provides a huge benefit in performance; reading and writing are way faster than persisting to disk. It also provides a way of persisting data utilizing RDB and AOF, making it perfect to keep the data when restarting the server.


Dependencies and Configuration

Your project will need spring-boot-starter-data-redis dependency in order to work properly with Spring Boot. Then we need to configure Redis and the cache, which is fairly easy to do it by taking advantage of Spring Boot’s auto configuration as shown in the following segment:

spring.redis.url=redis:// # Redis server host.
spring.redis.password= # Login password of the redis server. Leave empty if no password
spring.redis.port=6379 # Redis server port.

spring.cache.redis.cache-null-values=true # Allow caching null values.
spring.cache.redis.key-prefix=project_key # Key prefix.
spring.cache.redis.time-to-live=60000ms # Entry expiration. By default the entries never expire.
spring.cache.redis.use-key-prefix=true # Whether to use the key prefix when writing to Redis.

@EnableCaching is a required annotation set on a @Configuration class used to let spring scan all of the project’s files and execute the cache’s annotation action.


Let’s imagine that we are an unique token service provider and we have a list of clients that can generate these tokens for some reason. Each client must send an id to be validated before generating the token. The same client can generate millions of tokens, which means that it will try and get the client millions times; this is where the cache comes handy.

public class Client implemenets Serializable{
   private String clientId;
   private String clientName;
   //Getters, setters, etc...

Given this scenario, we need to have our service very optimized, just imagine millions of clients generating tokens; the server would collapse. Spring Boot provides a loose-coupled cache layer that uses annotations for caching, which means that we can use any cache implementation.

@Cacheable(value="client", key="#clientId")
public Client getClient(String clientId){
  return clientRepository.findById(clientId);

@CacheEvict(value="client", key="#clientId", condition = "#key != null")
public Client deleteClient(String clientId){
  return clientRepository.deleteById(clientId);

@CachePut(value="client", key="#client.clientId")
public Client saveOrUpdateClient(Client client){

  • @Cacheable allows the method getClient(id) to look for a client in the “client” cache by a given key before actually trying to execute the method.
  • @CacheEvict will delete any entry to the cache by the given key. I have added a condition to avoid any exception
  • @CachePut will always execute the method and store the result into the cache.

Useful annotations

There are other annotations that we can use in order to simplify our code or even solve a new scenario, like when we are going to update a client and we want to evict token’s cache at the same time. That’s when you need @Caching, it will allow you set multiple actions in one single method, like shown in the following snippet:

//  small example that will save a client into the cache and delete all tokens by the given client id
@Caching(@CachePut(value="client", key="#client.clientId"), 
         @CacheEvict(value="tokens", key="#client.clientId", allEntries=true))

We can also apply @CacheConfig in the of a class to configure the cache names and the key generator at the class level, which will save yourself the cost of adding the cache name to each annotation.

public class ClientService {...}