When you develop an API, you want to make it as efficient as possible. Every request to the server consumes computing power, bandwidth and increases latency. This is where HTTP caching comes into play. It allows the client (e.g. a browser or a proxy) to store certain responses locally and reuse them when needed instead of contacting the server each time.
The advantages of Spring Boot Caching:
- Reduced server load: fewer requests mean fewer calculations on the server.
- Faster response times: When data is loaded from the cache, there is no waiting time for a response from the server.
- Lower data consumption: Caching can bring considerable savings, especially for mobile users or with limited bandwidth.
- Better scalability: Systems can serve more users as they do not have to constantly recalculate the same data.
But be careful: not everything should be cached! If you have sensitive or frequently changing data (e.g. user profiles or bank transactions), you want to make sure that the client always gets the latest version of the data.
Learn more about the Spring Boot Cache here. So let’s get started right away.

The problem: Spring Boot automatically sets Cache-Control: no-cache
By default, Spring Boot and Spring Security in particular have the habit of setting Cache-Control: no-cache, no-store, must-revalidate in the headers. This means that browsers or proxies are not allowed to cache anything. Why?
- Security: With sensitive information such as login data or personalized content, you do not want an old, possibly incorrect cache value to be loaded.
- Correctness: Some data must always be up-to-date (e.g. inventory figures in an online store or user account balances).
The only problem is that not all API responses need this behavior. For example, if you return a static list of product categories or a JSON response with weather data, caching should be allowed.

Targeted control of cache control
Spring Boot allows us to explicitly set the Cache-Control header. If you want the client (browser or proxy) to cache your API responses for 1 minute, you can control this with the following method:
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(myProfileResponse);
The following happens here:
- maxAge(60, TimeUnit.SECONDS): The response may be cached for 60 seconds.
- The browser or a proxy can therefore load the response from the cache within this period of time without asking the server again.
If you do not want to set a Cache-Control header at all, you can use CacheControl.empty() instead:
return ResponseEntity.ok()
.cacheControl(CacheControl.empty())
.body(myProfileResponse);
This ensures that no Cache-Control header is included in the response. But be careful: If Spring Security is active, it still sets no-cache! Let’s take a look at that next.

Prevent Spring Boot caching? How to deactivate it in Spring Security.
If you use Spring Security, Cache-Control: no-cache is often set automatically, even if you explicitly try to remove it.To override this behavior, you can do the following:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.headers(headers -> headers.cacheControl(cache -> cache.disable()))
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.build();
}
The following happens here:
- .cacheControl(cache -> cache.disable()) switches off the automatic setting of Cache-Control: no-cache.
- Now you can decide for yourself whether you want to set the header or not.
If you still have problems with Cache-Control being enforced, you should remove the header manually.

Security aspects in caching
Avoid cache leaks
If sensitive data (e.g. user profiles or auth tokens) is returned via an API, it should be ensured that it is not cached:
return ResponseEntity.ok()
.cacheControl(CacheControl.noStore())
.body(sensitiveData);
Prevention of cache poisoning
Cache poisoning is a dangerous attack method in which manipulated content is injected into the cache in order to deliver it to users later. In Spring Boot applications, this can lead to significant security problems, especially when sensitive data is cached. In this section, I will show you how to effectively prevent cache poisoning.
To prevent personal data from being cached for multiple users, the Cache-Control header should be set to private. This ensures that only the original client can access the cached data.
return ResponseEntity.ok()
.cacheControl(CacheControl.privateCache())
.body(userProfileResponse);
The Vary header ensures that different variants of the same resource are saved based on specific headers. For example, an API response based on the Authorization header can only be cached for authenticated users.
return ResponseEntity.ok()
.header(HttpHeaders.VARY, "Authorization")
.body(userProfileResponse);
Secure implementation of ETags
If ETag is used, it should be ensured that no sensitive information is contained in the ETag:
String eTag = DigestUtils.md5DigestAsHex(responseBody.getBytes());
return ResponseEntity.ok()
.eTag(eTag)
.body(responseBody);

Remove cache control header completely
If you want to make absolutely sure that Cache-Control is not sent, you can also explicitly delete the header:
ResponseEntity<ProfileResponse> response = ResponseEntity.ok().body(myProfileResponse);
HttpHeaders headers = new HttpHeaders();
headers.putAll(response.getHeaders());
headers.remove("Cache-Control");
return new ResponseEntity<>(myProfileResponse, headers, HttpStatus.OK);
This method ensures that Cache-Control does not even appear in the response.
Conclusion & Best practices with Spring Boot Caching
✅ Set specific Cache-Control headers to define the desired cache duration.
✅ Use ETag and Last-Modified to enable more efficient caching.
✅ Avoid cache leaks by no-store for sensitive data.
✅ Protect yourself from cache poisoning with correct Vary headers.
✅ Use a global filter if you want to remove cache control from the entire app.
Use these techniques to ensure that your API responses are optimally cached & secure! 🚀