The name Redis often appears in cache-related articles, but many developers find it hard to take the first step and ask, “How do I actually use it with Spring Boot?”

This article starts with spinning up Redis locally using Docker Compose, then walks through basic RedisTemplate operations, session externalization with Spring Session, configuring Redis as the backend for @Cacheable, and implementing Pub/Sub — showing code organized by use case. The target is Spring Boot 3.x / Java 17 or later. Redis operations, tuning, and Cluster configurations are out of scope.

Spinning Up Redis Locally with Docker Compose

First, let’s prepare Redis in your local environment.

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

After starting with docker compose up -d, run docker compose exec redis redis-cli ping and you’re ready to go once PONG is returned. If you place it inside the same Compose as your Spring Boot container, you can use the service name redis directly as the hostname. For details on containerization, see How to Containerize a Spring Boot App with Docker.

Adding Dependencies and Configuring the Connection

Add spring-boot-starter-data-redis to your build.gradle.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

The connection settings in application.yml are simple.

spring:
  data:
    redis:
      host: localhost  # Use service name "redis" inside Docker Compose
      port: 6379
      # password: your-password
      connect-timeout: 2s
      timeout: 1s

Enabling /actuator/health is convenient because it lets you verify connectivity to Redis at startup all in one place.

Basic RedisTemplate Operations

If you only need to handle strings, StringRedisTemplate is convenient.

@Service
@RequiredArgsConstructor
public class CacheService {

    private final StringRedisTemplate redisTemplate;

    public void save(String key, String value, long ttlSeconds) {
        redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds));
    }

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public void updateExpire(String key, long ttlSeconds) {
        redisTemplate.expire(key, Duration.ofSeconds(ttlSeconds));
    }
}

If you also want to store POJOs, use RedisTemplate<String, Object> (configured in the next section).

Using GenericJackson2JsonRedisSerializer for Object Serialization

The default JdkSerializationRedisSerializer produces data on Redis that is hard to read and is brittle against class version changes. To store POJOs, use GenericJackson2JsonRedisSerializer. The stored JSON automatically gets a @class field added, so you don’t need to specify the type when deserializing. On the other hand, be aware that renaming or moving classes to a different package breaks compatibility with existing data.

When passing a custom ObjectMapper, you must explicitly call activateDefaultTyping(), otherwise POJOs come back as LinkedHashMap. Adding it embeds POJO type information in the JSON, so the correct type is restored when reading. For cases where you only store strings, Jackson2JsonRedisSerializer is sufficient.

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        var template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

        var objectMapper = new ObjectMapper()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
            .activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL
            );
        var serializer = new GenericJackson2JsonRedisSerializer(objectMapper);

        template.setDefaultSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        return template;
    }
}

Externalizing Sessions to Redis with Spring Session

By introducing spring-session-data-redis, the actual HttpSession is stored in Redis, allowing you to share sessions across multiple instances behind a load balancer. Since this simply replaces Tomcat’s in-memory sessions, you don’t need to modify your existing controller implementations.

When you scale out to multiple instances, sessions held in per-server memory are not shared. Solve this with Spring Session + Redis.

Assuming spring-boot-starter-data-redis is already added, add the following additional dependency.

implementation 'org.springframework.session:spring-session-data-redis'

In Spring Boot 3.x, simply adding configuration to application.yml enables session externalization. This is the simplest approach.

spring:
  session:
    store-type: redis
    timeout: 30m

If you want fine-grained control over timeouts and settings in code, you can also use @EnableRedisHttpSession. However, in Spring Session 3.x, @EnableRedisIndexedHttpSession is the new recommended annotation. To avoid conflicts with the application.yml auto-configuration, stick with one or the other.

With this setup, sessions can be read correctly no matter which instance receives the request. For stateless authentication using JWT, see also Implementing Spring Security + JWT Authentication.

Using Redis as the Backend for @Cacheable

By defining a RedisCacheManager, you can use Redis as the backend for @Cacheable.

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        var defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .disableCachingNullValues();

        var perCacheConfig = Map.of(
            "products", defaultConfig.entryTtl(Duration.ofHours(1)),
            "rankings", defaultConfig.entryTtl(Duration.ofMinutes(1))
        );

        return RedisCacheManager.builder(factory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(perCacheConfig)
            .build();
    }
}

Being able to set individual TTLs per cache name is convenient. For detailed usage of @Cacheable, see Implementing Caching with Spring Cache’s @Cacheable.

Implementing Pub/Sub Message Send/Receive via Redis

Redis Pub/Sub can be used for simple notifications and event distribution. Be aware that since messages are not persisted, any message published while no subscriber is connected is lost.

@Slf4j
@Component
public class NotificationListener {
    public void handleMessage(String message) {
        log.info("Received: {}", message);
    }
}

@Configuration
public class PubSubConfig {

    @Bean
    public RedisMessageListenerContainer listenerContainer(
            RedisConnectionFactory factory, MessageListenerAdapter adapter) {
        var container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(adapter, new PatternTopic("notification:*"));
        return container;
    }

    @Bean
    public MessageListenerAdapter messageListenerAdapter(NotificationListener listener) {
        return new MessageListenerAdapter(listener, "handleMessage");
    }
}

With RedisTemplate<String, Object>, the serializer gets involved and the message ends up with JSON quotes for type information attached. So for publishing, use convertAndSend() on StringRedisTemplate.

// Use StringRedisTemplate (RedisTemplate<String,Object> is NG)
stringRedisTemplate.convertAndSend("notification:user", "Logged in");

Distinguishing usage by prefix in channel names makes management easier. If you need full-fledged asynchronous processing, see also Spring Boot Async Processing.

ReactiveRedisTemplate (For WebFlux Users)

If you are using WebFlux, you can use ReactiveRedisTemplate. It differs from the imperative version in that opsForValue().set() returns Mono<Boolean>.

ReactiveRedisTemplate is auto-configured even in Spring MVC apps, but practical benefits are limited to WebFlux projects where you integrate it into Reactor’s Mono/Flux chains. If a spring-boot-starter-webflux dependency already exists, ReactiveRedisConnectionFactory is auto-configured, so no additional Bean definition is needed. For a typical Spring MVC app, RedisTemplate is sufficient.

Checkpoints When You Get Connection Errors

If RedisConnectionFailureException occurs at startup, check in this order.

  1. Whether the hostname and port in application.yml are correct
  2. Whether the Redis container is running (docker compose ps)
  3. Whether Spring Boot and Redis are on the same Docker network
  4. Check Redis status via /actuator/health

If ClassCastException occurs, old data may remain after changing the serializer settings. Only in development environments, clearing the entire DB with redis-cli FLUSHDB often resolves it. In production, delete specific keys with DEL <key>.

Summary

We’ve introduced the main patterns for using Redis with Spring Boot, organized by use case.

  • RedisTemplate for CRUD operations on strings and objects
  • Spring Session to externalize sessions and support scale-out
  • RedisCacheManager to use as the backend for @Cacheable
  • Pub/Sub to implement simple event notifications

It’s recommended to start by trying set/get with StringRedisTemplate. The natural progression is to add Spring Session when you need scale-out, and introduce RedisCacheManager once performance becomes a concern.

Session expiration and Cookie attributes can be controlled together in application.yml.

spring:
  session:
    store-type: redis
    timeout: 30m
    redis:
      namespace: myapp:session
server:
  servlet:
    session:
      cookie:
        name: MYAPPSESSIONID
        http-only: true
        secure: true       # Always true in HTTPS environments
        same-site: lax

Specifying namespace makes Redis keys take the form myapp:session:sessions:<id>, avoiding collisions when multiple applications share the same Redis. Remember that spring.session.timeout takes precedence over server.servlet.session.timeout if you want to override it.

Behavior at Login/Logout

When combined with Spring Security, the setting that regenerates the session ID on successful login (sessionFixation().migrateSession() is the default) works as-is with spring-session-data-redis. On logout, session keys on Redis are automatically deleted via HttpSession.invalidate().

If you want to forcibly log out active sessions from an admin screen, you can inject FindByIndexNameSessionRepository to search and delete by username.

@Service
@RequiredArgsConstructor
public class SessionAdminService {

    private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;

    public void logoutAllSessions(String username) {
        var sessions = sessionRepository.findByPrincipalName(username);
        sessions.keySet().forEach(sessionRepository::deleteById);
    }
}

To use this feature, you need to enable @EnableRedisIndexedHttpSession to create session indexes on Redis.

Verifying Session Sharing Across Multiple Instances

To verify the behavior locally, start two instances of the same app on different ports and distribute requests to each.

SERVER_PORT=8080 ./gradlew bootRun &
SERVER_PORT=8081 ./gradlew bootRun &

# Log in on 8080 and grab the Cookie
curl -c cookie.txt -X POST http://localhost:8080/login -d 'username=alice&password=...'

# Access 8081 with the same Cookie to check that the session is shared
curl -b cookie.txt http://localhost:8081/me

If only one session key is created when you run redis-cli KEYS 'myapp:session:sessions:*', both instances are referencing the same session. In production, you’d reproduce this behind a load balancer.