Problem
While Upgrading from Spring Boot 3.5.8 to 4.0.0, I'm seeing minimal @WebMvcTest tests and other test slices (e.g. @DataJdbcTest) fail with the following exception.
No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.
at app//org.springframework.cache.interceptor.CacheAspectSupport.afterSingletonsInstantiated(CacheAspectSupport.java:287)
at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1147)
at app//org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:983)
at app//org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:620)
at app//org.springframework.boot.SpringApplication.refresh(SpringApplication.java:765)
at app//org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:454)
at app//org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
Example
I've included the abbreviated example below to demonstrate the problem.
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockitoBean
private ProductService productService;
@Test
void getProduct_WhenProductExists_ReturnsProduct() throws Exception {
Product product = new Product(1L, "Test Product", "Test Description", 99.99);
when(productService.getProductById(1L)).thenReturn(Optional.of(product));
mockMvc.perform(get("/api/products/1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Test Product"))
.andExpect(jsonPath("$.description").value("Test Description"))
.andExpect(jsonPath("$.price").value(99.99));
}
}
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return productService.getProductById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
@Service
public class ProductService {
private final Map<Long, Product> dataStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public ProductService() {
dataStore.put(1L, new Product(1L, "Sample Product", "A sample product", 99.99));
dataStore.put(2L, new Product(2L, "Another Product", "Another sample", 149.99));
idGenerator.set(3);
}
@Cacheable(value = "products", key = "#id")
public Optional<Product> getProductById(Long id) {
System.out.println("Fetching product from data store for id: " + id);
return Optional.ofNullable(dataStore.get(id));
}
}
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("products");
}
}
@EnableCaching
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Full Reproducer: * Spring boot 3.5.8 - test, which passes. * Spring boot 4.0.0 - test, which fails.
What appears to be happening is these test slices scan the @SpringBootApplication to pick up configurations and sees the @EnableCacheing and then aggressively tries to autoconfigure cacheing.
In Spring Boot 3.x, if cacheing was not configured, it would silently ignore @Cacheable annotation. But in Spring Boot 4.x it fails the test.
Expectation
OSS tests slices and minimal test slices should not fail when trying to configure AOP integrations like cacheing when the dependencies and configurations are provided not provided those annotations to work.
The workaround is move @EnableCacheing to another @Configuration so it's not picked up by OSS Test slice annotations. However, this is not ideal or intuitive for the user to be aware of delicate separations like this. This is one example, but there's probably many ways this fails if @EnableCacheing is seen in a minimal test, but we don't want to intentionally test Cacheing.
Comment From: snicoll
Thanks for the report and the sample.
What appears to be happening is these test slices scan the @SpringBootApplication to pick up configurations and sees the @EnableCacheing and then aggressively tries to autoconfigure cacheing.
That's not what's happening. In your case, since you have put @EnableCaching on your main class, it is being processed in both Spring Boot 3.5 and Spring Boot 4.0. There is a note in the documentation. In your case, you really should put @EnableCaching on your CacheConfig.
In Spring Boot 3.5, slice tests were annotated with @AutoConfigureCache which automatically configures an in-memory cache that replaces whatever you've defined. This explains why this work in 3.5, and not in 4.0. That being said, I don't recall exactly what've decided to do with @AutoConfigureCache and I see no mention in the documentation so we'll use this issue to figure that out.
If you insist on putting @EnableCaching on your app, then you'll have to do something about slice tests requiring a CacheManager. I've annotated ProductControllerTest with @AutoConfigureCache and the test passes in 4.0 (with spring-boot-starter-cache-test added to the test classpath).
Comment From: iparadiso
Thank you for the quick and detailed response.
I appreciate the clarification about how @EnableCaching is picked up and the historical behavior of @AutoConfigureCache in test slices.
However, I want to highlight that for a long time, the standard guidance (including Spring Boot tutorials) has been to place @EnableCaching on the main @SpringBootApplication class. This has been the default in many codebases, and it aligns with how other cross-cutting annotations (like @EnableAsync or @EnableScheduling) are typically used. I understand best practices evolve but it takes time to catch up with existing code.
Now requiring either @AutoConfigureCache w/test starter, or moving @EnableCacheing to a dedicated @Configuration and pulling it in or supplying the NoOp config requires careful refactoring throughout the code base and tests.
To me this feels similar to the @AutoConfigureObservability change in 3.x that was more straight forward to supply a NoOp bean supplier via config properties, such as is the case now with spring.test.metrics.export=true or spring.test.tracing.export=true.
Would you be open to also adding config-driven support as well to supply a NoOpCacheManager if no other bean exists for platform code to set as a default?