DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • A Practical Guide to Semantic Caching With Redis LangCache
  • Caching Mechanisms Using Spring Boot With Redis or AWS ElastiCache
  • Scaling in Practice: Caching and Rate-Limiting With Redis and Next.js
  • Caching RESTful API Requests With Heroku Data for Redis

Trending

  • Detecting Bugs and Vulnerabilities in Java With SonarQube
  • No More Cheap Claude: 4 First Principles of Token Economics in 2026
  • How AI Is Rewriting Full-Stack Java Systems: Practical Patterns with Spring Boot, Kafka and WebSockets
  • Solving the Mystery: Why Java RSS Grows in Docker on M1 Macs
  1. DZone
  2. Data Engineering
  3. Data
  4. Caching Issues With the Spring Expression Language

Caching Issues With the Spring Expression Language

Spring Expression Language is a flexible way to evaluate expressions at runtime. However, in the context of caching, this flexibility can lead to errors.

By 
Constantin Kwiatkowski user avatar
Constantin Kwiatkowski
·
Jan. 20, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
2.0K Views

Join the DZone community and get the full member experience.

Join For Free

Let's imagine a web application where, for each request, it must read configuration data from a database. That data doesn't change usually, but the application, in each request, must connect, execute the correct instructions to read the data, pick it up from the network, etc. Imagine also that the database is very busy or the connection is slow. What would happen? We would have a slow application because it is reading continuously data that hardly changes. A solution to that problem could be using a cache within the Spring framework. 

Spring caching is based on a simple principle:

  • A cache key is calculated
  • If an entry for this key exists → return from the cache
  • If no entry exists → the method is executed and the result is stored

The cache key is crucial, and this is where SpEL plays a central role.

The Spring Expression Language (SpEL) is a powerful and flexible tool within the Spring Framework that enables the evaluation and manipulation of expressions at runtime. It provides a dynamic way to access objects and their properties, perform calculations, and even implement complex logical operations — all at runtime. SpEL can be used with both XML-based and annotation-based Spring configurations, simplifying development by reducing boilerplate code while enhancing flexibility. With its ability to assign values dynamically at runtime, SpEL offers an elegant solution to many common software development challenges and contributes to increased efficiency. For more information, check out the DZone article on learning the Spring Expression Language. SpEL is an expression language that allows you to:

Access method parameters (#id), Read object properties (#user.name), Invoke methods (#list.size()), Use static classes (T(java.lang.Math).PI), Formulate logical and conditional expressions. 

In caching, SpEL is primarily used for calculating cache keys.

Java
 
@Cacheable(value = "users", key = "#id")
public User findUser(Long id) {
    ...
}


Different Caching Issues When Using SpEL 

1. Unstable Cache Keys 

A common mistake is using non-deterministic expressions.

Java
 
@Cacheable(value = "orders", key = "T(java.util.UUID).randomUUID()")


Problem: The key is different with every call, meaning the cache is never hit and caching is not implemented.  The better way is:

Java
 
@Cacheable(value = "orders", key = "#orderId")


2. Use of Mutable Object States 

When SpEL accesses complex or mutable objects, the same method call can generate different cache keys.

Java
 
@Cacheable(value = "products", key = "#product")


Problem: equals() / hashCode() may not be stable, and the object's state changes after caching, leading to multiple cache entries for the 'same' data. The solution is to cache based on an object's field:

Java
 
@Cacheable(value = "products", key = "#product.id")


3. Different Keys in @Cacheable and @CacheEvict 

A very dangerous mistake occurs when different SpEL expressions are used for reading and evicting.

Java
 
@Cacheable(value = "users", key = "#user.id")
public User getUser(User user) { ... }

@CacheEvict(value = "users", key = "#user.username")
public void updateUser(User user) { ... }


Problem: The cache entry is never invalidated, and outdated data remains in the cache. A possible solution would be to always use the identical key strategy, ideally a central ID.

4. Null Values and Exceptions 

SpEL expressions can fail when values are null.

Java
 
@Cacheable(value = "users", key = "#user.address.city")


Problem: NullPointerException Due to a missing address, the cache mechanism failed. To avoid such issues, you could, for example, add additional conditions:

Java
 
@Cacheable(value = "users", condition = "#user != null")


5. Performance Issues Due to Complex SpEL Expressions 

SpEL is evaluated on every method call. Complex expressions can incur measurable performance costs.

Java
 
@Cacheable(
  value = "data",
  key = "#a.id + '-' + #b.name + '-' + T(java.time.Instant).now()"
)


Problem: Expensive evaluation, the key changes constantly, resulting in poor performance and no cache hits.

Recommendation: Keep SpEL simple, avoid method calls with side effects, and refrain from using time-based or random values.

Caching With Redis and Caffeine: A Practical Guide 

1. Caching With Redis

Redis is a distributed in-memory data store that is ideal for caching. It is often used in scalable, distributed applications where caching needs to be shared across multiple instances. 

First, we need to add the Redis and Spring Cache dependencies to the pom.xml (for Maven):

Shell
 
<dependencies>
    <!-- Spring Cache und Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>


Next, we add the Redis connection settings in application.properties:

Shell
 
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password= # falls Passwort gesetzt ist


In the Spring configuration, we need to configure the CacheManager and the RedisConnectionFactory.

Java
 
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  
                .disableCachingNullValues();
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(cacheConfiguration())
                .build();
    }
}


Now we can use the @Cacheable annotation to enable the caching mechanism:

Java
 
@Service
public class UserService {

    @Cacheable(value = "users", key = "#userId")
    public User getUserById(Long userId) {
        simulateSlowService(); 
        return new User(userId, "John Doe"); 
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(3000);  
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}


@Cacheable: This annotation ensures that the method's return value is stored in the cache. When the same method is called with the same parameters, the value is returned directly from the cache instead of executing the method again.

 value specifies the name of the cache, and the key parameter is used to compute the cache key.

When you call the getUserById method with a specific userId, the value is stored in the Redis cache. On the next call to the same method with the same userId, the value is returned directly from the cache, significantly improving performance.

2. Caching With Caffeine

Caffeine is an in-memory cache that is faster and easier to configure than Redis, as it doesn't rely on network communication and requires no additional infrastructure. Add Caffeine as a dependency in the pom.xml:

Shell
 
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.0.0</version>
</dependency>


Create a configuration class for caching with Caffeine:

Java
 
@Configuration
@EnableCaching
public class CaffeineCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)  
                .maximumSize(1000));  
        return cacheManager;
    }
}


The usage of the @Cacheable annotation is the same as in the Redis example, but now Caffeine is used to store the cache in memory:

Java
 
@Service
public class ProductService {

    @Cacheable(value = "products", key = "#productId")
    public Product getProductById(Long productId) {
        simulateSlowService();   
        return new Product(productId, "Product A");
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(2000);   
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}


When the getProductById method is called, and the return value is stored in the Caffeine cache. If you call the method again with the same parameters, the result is retrieved immediately from the cache. 

Conclusion

Spring Expression Language is a powerful tool, but when used with caching, it can quickly lead to hard-to-find bugs. The most common causes are unstable cache keys, inconsistent SpEL expressions, and overly complex or dynamic calculations.

Those who use SpEL in caching consciously and sparingly can benefit from high flexibility without performance or consistency issues. Therefore, use primitive or simple keys (ID, String, Long), ensure consistent cache keys across @Cacheable, @CachePut, and @CacheEvict, and avoid random values, timestamps, and complex object graphs. SpEL expressions should be short, stable, and deterministic, and the cache behavior should be explicitly tested (hits & misses). For clean caching with SpEL, it is best to use an established framework like Redis or Caffeine. Depending on the use case, these frameworks have their own advantages and disadvantages.

FEATURE
REDIS
CAFFEINE
Usage
Distributed cache (over networks)
Local cache in memory
Performance
Slow latency due to network access
Very fast, as it's in memory
Scalability
Very good for distributed systems
For local caches on a single instance
Storage Size
Very large, can store massive data Limited size based on memory
Infrastructure
Requires Redis server (external infrastructure) No additional infrastructure needed
Complexity
Slightly more complex to manage
Simple and lightweight

Redis is ideal for distributed systems and applications that need to scale, especially when many instances are involved, and data must be shared across multiple servers. Caffeine is perfect for local caches where performance is crucial, and the application does not require a distributed infrastructure.

Both caching solutions integrate seamlessly with Spring and provide an easy way to improve your application's performance through SpEL-based caching!

Cache (computing) Redis (company)

Opinions expressed by DZone contributors are their own.

Related

  • A Practical Guide to Semantic Caching With Redis LangCache
  • Caching Mechanisms Using Spring Boot With Redis or AWS ElastiCache
  • Scaling in Practice: Caching and Rate-Limiting With Redis and Next.js
  • Caching RESTful API Requests With Heroku Data for Redis

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook