Unlock Your Python Backend Career: Build 30 Projects in 30 Days. Join now for just $54

Spring Boot + Redis + Docker: Ultimate Guide to Caching in Java

by Ayush Shrivastava

.

Updated Wed Apr 09 2025

.
Spring Boot + Redis + Docker: Ultimate Guide to Caching in Java

With Redis, your Spring Boot app can become faster and handle more traffic without breaking a sweat. This article will show you how to set it up step by step. However, before diving deep into Redis, we need to understand the concept of caching.

What is Cache?

Cache is a fast, small, temporary storage frequently used by the computer or application to store and access important data.

It stores data in a key-value format. By leveraging cache memory, we can minimize database calls, improving application performance since database queries are typically resource-intensive.

The main objective of a cache is to speed up the retrieval of data by making a copy of the data in a location that can be accessed faster than the source or database.

A cache is a small and fast, and efficient memory space that an application frequently uses to store or access important data.

image (8).pngWhy Caching?

The main objective of a cache is to speed up the retrieval of data by making a copy of the data in a location that can be accessed faster than the source or database.

In our application, whenever multiple requests access static data (data that is not changed frequently), we fetch the data from the database every time. Therefore, the number of database calls increases, which affects the performance of our application because database calls are always costly.

However, the static data can be stored in a cache, and whenever a request is made to access the data, it is fetched from the cache. As a result, the number of database calls is reduced, and the application's performance is improved.

How does a Cache work?

In the diagram above, multiple requests made to access the data in the application will first check the cache to determine whether the data is present. If the data is found, it is returned from the cache, a concept known as a Cache Hit. If the data is not found, it is retrieved from the database, which is referred to as a Cache Miss.

Cache Hit

Data is found in the cache, so it has to be fetched from a faster source.

We can understand the cache hit like this: imagine you have a notebook where you write down answers to questions you frequently ask. You ask a question, and the answer is already written in your notebook. You quickly find it without searching elsewhere.

Cache Miss

Data is not found in the cache, so it has to be fetched from a slower source.

Similarly, you asked a question, but it’s not in your notebook. You have to search in a big textbook (which takes more time) and then write the answer in your notebook for future use.

Redis

redis.webpRedis (Remote Dictionary Server) is an open-source, in-memory key-value data store that supports various data structures, including strings, lists, sets, and hashes. Its in-memory architecture ensures high performance, making it an ideal choice for caching and session management in modern applications.

The spring-boot-starter-redis is a Spring Boot starter that simplifies integrating Redis into Spring applications. It includes all the necessary dependencies to connect, configure, and operate with Redis.

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring Boot provides the spring-boot-starter-redis starter, enabling seamless communication with the Redis server. It includes various components that facilitate efficient interaction with Redis.

  • JedisConnectionFactory: Manages and establishes the connection between the Spring Boot application and the Redis server.

  • RedisTemplate: It provides methods to perform operations like saving, retrieving, and deleting data in Redis.

  • StringRedisTemplate: A specialized version of RedisTemplate that simplifies operations for String-based keys and values.

  • OpsForValue: Supports operations on simple key-value pairs in Redis.

  • OpsForHash: It is providing methods to perform operations based on the Hash Data structure.

  • OpsForList: Provides methods to interact with Redis Lists.

  • OpsForSet: Facilitates operations on Redis Sets.

Spring Boot with Redis Integration

Spring Boot provides seamless integration with Redis, an in-memory data store, through the spring-boot-starter-redis starter. It simplifies configuration and enables developers to use Redis for caching, messaging, and data persistence.

Prerequisites for Running the Spring Boot Redis Integration Project

  • Redis Server (Local or Cloud-based; here we use Docker)

  • Java Development Kit (JDK 17 or above)

  • Maven (Build tool)

  • IDE (e.g., IntelliJ IDEA, Eclipse, or Spring Tool Suite)

  • Postman (Optional, for testing REST APIs)

  • Project Build Using Maven

Docker Redis Setup

In this article, we install Redis using Docker. We can also manually download it from the Redis website, but here, we download the latest Redis image from Docker Hub (ensure Docker is already installed on your machine).

We have a sample Spring Boot application that you can clone from GitHub for initial setup. The initial code is available in the main branch, so please check out the main or redis integration branch. I also added the Postman collection to the root directory of the project so you can test all the API before integration.

Steps to Integrate Redis with Spring Boot

  1. Add the Maven dependency in the pom.xml file. Since we are using Docker, we also include the Docker Compose dependency.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-docker-compose</artifactId>
    <scope>runtime</scope>
</dependency>

2. Configure Redis Connection or database connection (we’re using H2 as database). Add the Redis server configuration to application.properties or application.yml

spring.application.name=spring-boot-redis-cache

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

spring.cache.type=redis
spring.data.redis.host=localhost
spring.data.redis.port=6379

# if we are using local redis or cloud but here we use docker so there is no need of username or password
spring.data.redis.username=
spring.data.redis.password=

3. Create the docker-compose.yml file in the root folder with the same naming convention we follow for docker-redis configurations.

services:
  redis:
    image: redis:7.4.2
    ports:
      - 6379:6379

4. Mark the Spring Boot application class as @EnableCaching to enable caching in our Spring Boot application. To enable caching in our Spring Boot application, add the @EnableCaching annotation to one of our configuration classes. This annotation triggers a post-processor that inspects each Spring bean for caching annotations.

package com.ayshriv.springbootrediscache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class SpringBootRedisCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRedisCacheApplication.class, args);
    }

}

5. Create the class RedisConfig.class inside the config package

package com.ayshriv.springbootrediscache.config;

import com.techie.springbootrediscache.dto.ProductDto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

@Configuration  // Marks this class as a Spring configuration class
public class RedisConfig {

    @Bean  // Defines a Spring bean for RedisCacheManager
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        // Define cache configuration
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)) // Set time-to-live (TTL) for cache entries to 10 minutes
                .disableCachingNullValues() // Prevent caching of null values
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new Jackson2JsonRedisSerializer<>(ProductDto.class))); // Serialize values using Jackson JSON serializer

        // Create and return a RedisCacheManager with the specified configuration
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfig) // Apply default cache configuration
                .build();
    }
}

This class configures Redis caching in a Spring Boot application. The @Configuration annotation marks it as a configuration class, and the @Bean annotation defines a RedisCacheManager bean.

The cache configuration includes a time-to-live (TTL) of 10 minutes (Duration.ofMinutes(10)), which ensures that cached data expires automatically after this period. It also disables the caching of null values (disableCachingNullValues()) to optimize memory usage.

For serialization, the Jackson2JsonRedisSerializer is used, which converts Java objects (ProductDto) to JSON before storing them in Redis. This ensures that cached data remains structured and readable.

The RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfig).build() the method initializes the cache manager with the given configuration and connects it to the Redis database using RedisConnectionFactory.

6. After doing all the configurations, we implement the caching on the business class (service class).

package com.ayshriv.springbootrediscache.service;

import com.techie.springbootrediscache.dto.ProductDto;
import com.techie.springbootrediscache.entity.Product;
import com.techie.springbootrediscache.repository.ProductRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @CachePut(value="PRODUCT_CACHE", key="#result.id")
    public ProductDto createProduct(ProductDto productDto) {
        var product = new Product();
        product.setName(productDto.name());
        product.setPrice(productDto.price());

        Product savedProduct = productRepository.save(product);
        return new ProductDto(savedProduct.getId(), savedProduct.getName(),
                savedProduct.getPrice());
    }

    @Cacheable(value="PRODUCT_CACHE", key="#productId")
    public ProductDto getProduct(Long productId) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new IllegalArgumentException("Cannot find product with id " + productId));
        return new ProductDto(product.getId(), product.getName(),
                product.getPrice());
    }

    @CachePut(value="PRODUCT_CACHE", key="#result.id")
    public ProductDto updateProduct(ProductDto productDto) {
        Long productId = productDto.id();
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new IllegalArgumentException("Cannot find product with id " + productId));

        product.setName(productDto.name());
        product.setPrice(productDto.price());

        Product updatedProduct = productRepository.save(product);
        return new ProductDto(updatedProduct.getId(), updatedProduct.getName(),
                updatedProduct.getPrice());
    }

    @CacheEvict(value="PRODUCT_CACHE", key="#productId")
    public void deleteProduct(Long productId) {
        productRepository.deleteById(productId);
    }
}

When we mark the createProduct() method with this @CachePut(value = "PRODUCT_CACHE", key = "#result.id") annotation, this ensures that the object returned by this method is stored or updated in the cache named PRODUCT_CACHE, and the key part specifies that the cache key should be the ID of the returned ProductDto object.

This is useful in scenarios where a new product is created or an existing product is updated, as it ensures that the latest product details are stored in the cache. By using this annotation, subsequent requests for the same product can be served quickly from the cache, reducing database queries and improving application performance while keeping the cache up to date.

Next, when we mark the getProductById() method with this @Cacheable(value = "PRODUCT_CACHE", key = "#productId") annotation, this ensures that the returned object by this method is stored in the cache named "PRODUCT_CACHE", and the key The part specifies that the cache key should be the productId parameter passed to the method.

If the requested productId is already present in the cache, the method skips execution and directly returns the cached value, avoiding a database call. However, if the productId is not found in the cache, the method executes, retrieves the product from the database, stores the result in the cache, and then returns it.

When we mark the deleteProduct() method with this @CacheEvict(value = "PRODUCT_CACHE", key = "#productId") annotation, this ensures that the cache entry associated with the given productId is removed from the cache named "PRODUCT_CACHE". The key part specifies that the cache key to be evicted is the productId parameter passed to the method.

This means that after deleting a product from the database, its cached entry will also be removed, ensuring that stale data is not served from the cache in future requests. If the deleted product is requested again, it will be fetched from the database and re-cached if applicable.

Here, we do all the things with the help of the annotation-based approach, but we can also do the same things with the help of CacheManager

The CacheManager It is a Spring framework interface responsible for managing different cache implementations. It acts as a central mechanism to store, retrieve, and manage cached data efficiently..

private final CacheManager cacheManager;

public ProductDto createProduct(ProductDto productDto) {
        var product = new Product();
        product.setName(productDto.name());
        product.setPrice(productDto.price());

        Product savedProduct = productRepository.save(product);
        Cache productCache = cacheManager.getCache("PRODUCT_CACHE");
        productCache.put(savedProduct.getId(), savedProduct);
        
        return new ProductDto(savedProduct.getId(), savedProduct.getName(),
                savedProduct.getPrice());
    }

Test the application

1. ADD-PRODUCT: http://localhost:8080/api/product

image (9).png2. GET-PRODUCT: http://localhost:8080/api/product/3

image (10).png3. UPDATE PRODUCT: http://localhost:8080/api/product

image (11).png4. DELETE PRODUCT: http://localhost:8080/api/product/2

image (12).pngConclusion

Redis with Spring Boot makes apps faster by caching data and reducing database calls. Using @Cacheable, @CachePut, and @CacheEvict, We can easily store, update, and delete cached data. This improves speed, reduces server load, and helps the app handle more users smoothly.

When we are performing the create operation, the response of this operation is stored in the cache, so whenever we perform operations like the getProduct() It checks the product in the cache first; if the product is present inside the cache, it returns that; if not, then it will return from the database.

Similarly, when we perform the delete operation, first it deletes the data from the database and then deletes it from the cache as well. All these types of operations reduce the number of DB calls so the performance of the application is increased.

Course image
Become a Java Backend Engineeer today

All-in-one Java course for learning backend engineering with Java. This comprehensive course is designed for Java developers seeking proficiency in Java.

Start Learning Now

Whenever you're ready

There are 4 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Get Backend Jobs

Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board

Backend Tips, Every week

Backend Tips, Every week