A Redis key clearly exists, yet a Spring Boot application reads it back as null. The symptom feels strange at first, but the cause can be surprisingly ordinary: the value was written and read through different RedisTemplate beans.
This issue is easy to run into when using Spring Boot’s Redis auto-configuration, especially if @Autowired and @Resource are mixed in the same project.
Basic Redis setup in Spring Boot
Integrating Redis into a Spring Boot project is usually straightforward. Add the Redis starter to pom.xml, then configure the connection details.
Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Application configuration:
spring:
redis:
host: 127.0.0.1
port: 6379
database: 1
password: 123456
timeout: 5000
With that in place, Redis can already be used. A simple test might look like this:
@SpringBootTest
@RunWith(SpringRunner.class)
public class TokenTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void getValue() {
Object value = redisTemplate.opsForValue().get("1");
System.out.println("value:" + value);
}
}
Once RedisTemplate is injected, its built-in operations can be used directly. Spring Data Redis exposes plenty of APIs beyond this basic example.
The real reason a value comes back as null
The confusing part is that the data really is in Redis. The problem is not a missing key, but inconsistent serialization behavior caused by using different template beans when writing and reading.
Spring Boot’s Redis auto-configuration creates two templates by default. The key detail is visible in RedisAutoConfiguration:
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
There are two different beans here:
redisTemplate: typeRedisTemplate<Object, Object>stringRedisTemplate: typeStringRedisTemplate, which extendsRedisTemplate<String, String>
One is designed around Object values, the other around String values. If a value is stored through one template and retrieved through the other, the lookup may not behave as expected, and the result can appear as null.
Why this can be hard to spot
If the code obviously used two different types in two places, the issue would usually be found quickly. The harder part is that the mismatch can be hidden by the way dependency injection works.
In the problematic case, reading used @Resource:
@Resource
private RedisTemplate<String, String> redisTemplate;
Writing used @Autowired:
@Autowired
private RedisTemplate<String, String> redisTemplate;
At a glance, those fields even look identical. That is what makes the bug deceptive.
@Resource and @Autowired do not behave the same way
The difference comes from how Spring resolves the dependency:
@Autowiredinjects by type by default.@Resourcetries bean name first, then falls back to type if no matching name is found.
That distinction matters because Spring Boot has already registered both redisTemplate and stringRedisTemplate.
When @Autowired is used, Spring resolves the dependency by type. In this scenario, the injection ends up using the string-oriented template.
When @Resource is used on a field named redisTemplate, name matching happens first. Since there is a bean literally named redisTemplate, Spring injects that bean instead—and that bean is RedisTemplate<Object, Object>.
So even though both fields are declared as RedisTemplate<String, String>, the actual beans injected in the two places are different. One operation writes through one template, the other reads through another, and the read returns null.
How to fix it
Once the root cause is clear, the fix is simple.
One option is to replace @Resource with @Autowired so the injection strategy stays consistent.
Another option is to keep @Resource, but inject the correct bean explicitly by using the stringRedisTemplate bean name instead of redisTemplate.
Depending on the project, there may be other valid approaches, but the key is to make sure read and write operations use the same RedisTemplate bean.
What this issue really shows
Spring Boot makes Redis integration easy, but that convenience can hide a few details that matter later. The framework creates two Redis templates by default, and once that is combined with the naming-first behavior of @Resource, a subtle bug can emerge.
The safest approach is to keep injection style and Redis access conventions consistent across the project. Understanding the auto-configuration and the behavior of common annotations is often what turns a strange runtime issue into a very ordinary one.