Spring Expression Language (SpEL)

Complete guide to Spring Expression Language (SpEL): learn how to use SpEL for dynamic expression evaluation, property access, method invocation, and conditional logic in Spring applications. Master SpEL syntax and advanced features.

Table of Contents

1. What is SpEL?

Spring Expression Language (SpEL) is a powerful expression language that supports querying and manipulating object graphs at runtime. It provides a unified way to express values for bean property assignments, dependency injection, and various Spring configuration scenarios.

The following diagram illustrates how SpEL integrates with Spring and evaluates expressions:

graph TB subgraph "SpEL Expression Sources" A[XML Configuration] B[Value Annotation] C[Programmatic Evaluation] D[Security Expressions] E[Cache Keys] end subgraph "SpEL Parser" F[SpelExpressionParser] A --> F B --> F C --> F D --> F E --> F end subgraph "Expression Evaluation" F --> G[Parse Expression] G --> H[Create Evaluation Context] H --> I[Resolve Variables] I --> J[Execute Expression] J --> K[Type Conversion] end subgraph "Evaluation Context" L[Root Object] M[Variables] N[Functions] O[Bean References] end H --> L H --> M H --> N H --> O K --> P[Result Value] style A fill:#fff4e1,stroke:#f57c00,stroke-width:2px style B fill:#fff4e1,stroke:#f57c00,stroke-width:2px style C fill:#fff4e1,stroke:#f57c00,stroke-width:2px style F fill:#e1f5ff,stroke:#0273bd,stroke-width:3px style G fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style P fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

SpEL is similar to other expression languages like OGNL, MVEL, and JBoss EL, but it's designed specifically for the Spring ecosystem. It can be used independently or integrated into other Spring features.

Key characteristics of SpEL:

  • Runtime expression evaluation
  • Type conversion and coercion
  • Method invocation
  • Property access and navigation
  • Collection projection and selection
  • Template expression support
  • Bean reference resolution

2. Why Use SpEL?

  • Dynamic Configuration: Evaluate expressions at runtime instead of compile-time constants
  • Conditional Logic: Use ternary operators and conditional expressions in configuration
  • Property Access: Access nested properties and collections easily
  • Method Invocation: Call methods and constructors dynamically
  • Collection Operations: Filter, project, and transform collections
  • Type Safety: Automatic type conversion and coercion
  • Integration: Works seamlessly with Spring annotations and XML configuration
  • Flexibility: Supports complex expressions for advanced use cases

3. SpEL Syntax

SpEL expressions are written using a syntax similar to Java, with some additional features for Spring-specific operations.

The following diagram shows the main SpEL expression types and their usage:

graph TB subgraph "SpEL Expression Types" A[SpEL Expression] end subgraph "Literals" B[String Literals] C[Numeric Literals] D[Boolean Literals] E[Null Literal] end subgraph "Property Access" F[Dot Notation] G[Bracket Notation] H[Safe Navigation] end subgraph "Method Invocation" I[Instance Methods] J[Static Methods] K[Constructors] end subgraph "Operators" L[Arithmetic Operators] M[Relational Operators] N[Logical Operators] O[Conditional Operators] end subgraph "Collections" P[List Access] Q[Map Access] R[Selection] S[Projection] end A --> B A --> C A --> D A --> E A --> F A --> G A --> H A --> I A --> J A --> K A --> L A --> M A --> N A --> O A --> P A --> Q A --> R A --> S style A fill:#e1f5ff,stroke:#0273bd,stroke-width:3px style B fill:#fff4e1,stroke:#f57c00,stroke-width:2px style F fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style I fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style L fill:#fce4ec,stroke:#c2185b,stroke-width:2px style P fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px

3.1 Literals

SpEL supports various literal types:

// String literals
'Hello World'
"Hello World"

// Numeric literals
123          // Integer
123.45       // Double
1.23e-5      // Scientific notation

// Boolean literals
true
false

// Null literal
null

// Examples in annotations
@Value("#{'Hello World'}")
private String greeting;

@Value("#{123}")
private int number;

@Value("#{true}")
private boolean flag;

3.2 Property Access

Access object properties using dot notation or bracket notation:

// Dot notation
user.name
user.address.city

// Bracket notation (useful for dynamic property names)
user['name']
user['address']['city']

// Safe navigation (returns null if intermediate value is null)
user?.address?.city

// Examples
@Value("#{user.name}")
private String userName;

@Value("#{user.address.city}")
private String city;

@Value("#{user?.address?.city ?: 'Unknown'}")
private String safeCity;

3.3 Method Invocation

Call methods on objects using standard Java method call syntax:

// Method calls
user.getName()
user.getAddress().getCity()
Math.max(10, 20)

// Static method calls
T(java.lang.Math).random()
T(java.util.UUID).randomUUID()

// Constructor calls
new java.util.Date()
new com.example.User('John', 'Doe')

// Examples
@Value("#{user.getName()}")
private String name;

@Value("#{T(java.lang.Math).random()}")
private double random;

@Value("#{new java.util.Date()}")
private Date currentDate;

3.4 Operators

SpEL supports various operators for arithmetic, logical, relational, and conditional operations:

3.4.1 Arithmetic Operators

// Basic arithmetic
#{10 + 5}        // 15
#{10 - 5}        // 5
#{10 * 5}        // 50
#{10 / 5}        // 2
#{10 % 3}        // 1
#{10 ^ 2}        // 100 (power)

// Examples
@Value("#{100 + 50}")
private int sum;

@Value("#{price * quantity}")
private double total;

3.4.2 Relational Operators

// Comparison
#{10 == 10}      // true
#{10 != 5}       // true
#{10 < 20}      // true
#{10 <= 20}      // true
#{10 > 5}        // true
#{10 >= 5}       // true

// Instanceof
#{user instanceof com.example.User}

// Examples
@Value("#{age >= 18}")
private boolean isAdult;

3.4.3 Logical Operators

// Logical operations
#{true and false}    // false
#{true or false}     // true
#{not true}          // false
#{!true}             // false

// Examples
@Value("#{age >= 18 and age <= 65}")
private boolean isWorkingAge;

3.4.4 Conditional (Ternary) Operator

// Ternary operator
#{condition ? valueIfTrue : valueIfFalse}
#{age >= 18 ? 'Adult' : 'Minor'}
#{user != null ? user.name : 'Guest'}

// Elvis operator (null coalescing)
#{user.name ?: 'Unknown'}
#{value ?: defaultValue}

// Examples
@Value("#{age >= 18 ? 'Adult' : 'Minor'}")
private String category;

@Value("#{user?.name ?: 'Guest'}")
private String displayName;

3.5 Collections

SpEL provides powerful operations for working with collections:

3.5.1 List and Array Access

// List access
users[0]                    // First element
users[0].name              // Property of first element
users[users.size() - 1]    // Last element

// Array access
array[0]
array[0].property

// Examples
@Value("#{users[0].name}")
private String firstUserName;

@Value("#{items[0].price}")
private double firstItemPrice;

3.5.2 Map Access

// Map access
map['key']
map.key
map[keyVariable]

// Examples
@Value("#{config['database.url']}")
private String dbUrl;

@Value("#{settings.timeout}")
private int timeout;

3.5.3 Collection Selection and Projection

// Selection (filtering)
users.?[age > 18]              // All users over 18
users.?[name.startsWith('J')]  // Users whose name starts with 'J'

// Projection (transformation)
users.![name]                  // List of names
users.![name + ' ' + surname]  // List of full names

// Examples
@Value("#{users.?[age > 18]}")
private List<User> adults;

@Value("#{users.![name]}")
private List<String> names;

3.5.4 Collection Literals

// List literal
{1, 2, 3, 4, 5}
{'a', 'b', 'c'}

// Map literal
{'key1': 'value1', 'key2': 'value2'}

// Examples
@Value("#{{1, 2, 3, 4, 5}}")
private List<Integer> numbers;

@Value("#{{'admin': 'Admin', 'user': 'User'}}")
private Map<String, String> roles;

4. Using SpEL

SpEL can be used in various ways throughout Spring applications.

4.1 Annotation-Based Usage

The most common way to use SpEL is through the @Value annotation:

@Component
public class SpelExample {
    // Simple value
    @Value("#{100}")
    private int number;
    
    // Property access
    @Value("#{systemProperties['user.name']}")
    private String systemUser;
    
    // Environment property
    @Value("#{environment['app.name']}")
    private String appName;
    
    // Method call
    @Value("#{T(java.lang.System).currentTimeMillis()}")
    private long timestamp;
    
    // Conditional
    @Value("#{environment['app.env'] == 'prod' ? 'production' : 'development'}")
    private String environment;
    
    // Bean reference
    @Value("#{userService}")
    private UserService userService;
    
    // Bean method call
    @Value("#{userService.getDefaultUser().name}")
    private String defaultUserName;
    
    // Collection operations
    @Value("#{users.size()}")
    private int userCount;
    
    @Value("#{users.?[active == true]}")
    private List<User> activeUsers;
}

4.2 XML-Based Usage

SpEL can be used in XML configuration files:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- Simple value -->
    <bean id="calculator" class="com.example.Calculator">
        <property name="maxValue" value="#{100}"/>
    </bean>
    
    <!-- Property access -->
    <bean id="config" class="com.example.Config">
        <property name="timeout" value="#{systemProperties['timeout']}"/>
    </bean>
    
    <!-- Bean reference -->
    <bean id="service" class="com.example.Service">
        <property name="repository" value="#{userRepository}"/>
    </bean>
    
    <!-- Method call -->
    <bean id="factory" class="com.example.Factory">
        <property name="instance" value="#{factoryBean.createInstance()}"/>
    </bean>
    
    <!-- Conditional -->
    <bean id="dataSource" class="com.example.DataSource">
        <property name="url" value="#{environment['db.url'] ?: 'jdbc:default'}"/>
    </bean>
</beans>

4.3 Programmatic Usage

You can evaluate SpEL expressions programmatically using SpelExpressionParser:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

@Service
public class SpelEvaluationService {
    private final ExpressionParser parser = new SpelExpressionParser();
    
    public Object evaluate(String expression, Object rootObject) {
        Expression exp = parser.parseExpression(expression);
        StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
        return exp.getValue(context);
    }
    
    public <T> T evaluate(String expression, Object rootObject, Class<T> resultType) {
        Expression exp = parser.parseExpression(expression);
        StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
        return exp.getValue(context, resultType);
    }
    
    // Example usage
    public void example() {
        User user = new User("John", "Doe", 30);
        
        // Evaluate expression
        String name = evaluate("name", user, String.class);
        int age = evaluate("age", user, Integer.class);
        boolean isAdult = evaluate("age >= 18", user, Boolean.class);
        
        // Complex expression
        String fullName = evaluate("firstName + ' ' + lastName", user, String.class);
    }
}

5. Bean References

SpEL can reference Spring beans and their properties/methods:

5.1 Referencing Beans

// Reference bean by name
@Value("#{userService}")
private UserService userService;

// Reference bean by type (if unique)
@Value("#{userRepository}")
private UserRepository repository;

// Reference bean property
@Value("#{appConfig.databaseUrl}")
private String dbUrl;

// Reference bean method
@Value("#{userService.getDefaultUser().name}")
private String defaultUserName;

// Reference with null-safe navigation
@Value("#{userService?.getUser(1)?.name ?: 'Unknown'}")
private String userName;

5.2 Using @ in Expressions

// Reference bean by name using @
@Value("#{@userService}")
private UserService userService;

// Useful when bean name conflicts with property
@Value("#{@'userService'}")
private UserService service;

// Reference bean and call method
@Value("#{@userService.getUserCount()}")
private int userCount;

5.3 System Properties and Environment

// System properties
@Value("#{systemProperties['java.version']}")
private String javaVersion;

@Value("#{systemProperties['user.home']}")
private String userHome;

// Environment variables
@Value("#{environment['PATH']}")
private String path;

// Spring environment properties
@Value("#{environment['app.name']}")
private String appName;

@Value("#{environment['database.url']}")
private String databaseUrl;

6. Real-World Examples

6.1 Example 1: Dynamic Configuration

@Configuration
public class DynamicConfig {
    @Value("#{environment['app.env'] == 'prod' ? 10000 : 1000}")
    private int connectionTimeout;
    
    @Value("#{environment['app.env'] == 'prod' ? 'prod-db' : 'dev-db'}")
    private String databaseName;
    
    @Value("#{systemProperties['user.timezone'] ?: 'UTC'}")
    private String timezone;
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("#{environment['database.url']}");
        config.setConnectionTimeout(connectionTimeout);
        return new HikariDataSource(config);
    }
}

6.2 Example 2: Conditional Bean Creation

@Configuration
public class ConditionalConfig {
    @Bean
    @ConditionalOnExpression("#{environment['feature.email.enabled'] == 'true'}")
    public EmailService emailService() {
        return new EmailServiceImpl();
    }
    
    @Bean
    @ConditionalOnExpression("#{environment['feature.cache.enabled'] == 'true' and environment['cache.type'] == 'redis'}")
    public CacheManager redisCacheManager() {
        return new RedisCacheManager();
    }
}

6.3 Example 3: Collection Operations

@Component
public class UserService {
    @Value("#{users.?[active == true]}")
    private List<User> activeUsers;
    
    @Value("#{users.![name]}")
    private List<String> userNames;
    
    @Value("#{users.?[age >= 18].size()}")
    private int adultUserCount;
    
    @Value("#{users.?[role == 'ADMIN']}")
    private List<User> adminUsers;
    
    @Value("#{config.settings['max.users'] ?: 100}")
    private int maxUsers;
}

6.4 Example 4: Security Expressions

@Service
public class SecurityService {
    @PreAuthorize("#{@securityService.hasPermission(#userId, 'READ')}")
    public User getUser(Long userId) {
        return userRepository.findById(userId);
    }
    
    @PreAuthorize("#{authentication.name == #user.username or hasRole('ADMIN')}")
    public void updateUser(User user) {
        userRepository.save(user);
    }
    
    public boolean hasPermission(Long userId, String permission) {
        // Permission check logic
        return true;
    }
}

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    @PreAuthorize("#{@userService.isOwner(#id, authentication.name) or hasRole('ADMIN')}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
}

6.5 Example 5: Caching with SpEL

@Service
public class ProductService {
    @Cacheable(value = "products", key = "#id")
    public Product getProduct(Long id) {
        return productRepository.findById(id);
    }
    
    @Cacheable(value = "products", key = "#product.category + '_' + #product.id")
    public Product getProductByCategory(Product product) {
        return productRepository.findByCategory(product.getCategory(), product.getId());
    }
    
    @CacheEvict(value = "products", key = "#product.id")
    public void updateProduct(Product product) {
        productRepository.save(product);
    }
    
    @CacheEvict(value = "products", allEntries = true, condition = "#clearAll == true")
    public void clearCache(boolean clearAll) {
        // Cache cleared conditionally
    }
}

6.6 Example 6: Scheduling with SpEL

@Configuration
@EnableScheduling
public class SchedulingConfig {
    @Value("#{environment['scheduler.enabled'] == 'true'}")
    private boolean schedulerEnabled;
    
    @Scheduled(cron = "#{environment['scheduler.cron'] ?: '0 0 * * * *'}")
    public void scheduledTask() {
        if (schedulerEnabled) {
            // Execute task
        }
    }
    
    @Scheduled(fixedDelayString = "#{environment['scheduler.delay'] ?: 5000}")
    public void fixedDelayTask() {
        // Execute task
    }
}

7. Best Practices

7.1 Keep Expressions Simple

Prefer simple expressions over complex ones. If an expression becomes too complex, consider moving the logic to a Java method.

7.2 Use Null-Safe Navigation

Use the ?. operator to avoid NullPointerException when navigating object graphs.

// Good
@Value("#{user?.address?.city ?: 'Unknown'}")

// Avoid
@Value("#{user.address.city}") // May throw NPE

7.3 Provide Default Values

Use the Elvis operator (?:) to provide default values for potentially null expressions.

@Value("#{environment['app.name'] ?: 'MyApp'}")
private String appName;

7.4 Cache Expression Parsing

When using SpEL programmatically, cache parsed expressions for better performance.

@Service
public class CachedSpelService {
    private final ExpressionParser parser = new SpelExpressionParser();
    private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
    
    public Object evaluate(String expression, Object root) {
        Expression exp = expressionCache.computeIfAbsent(
            expression,
            parser::parseExpression
        );
        return exp.getValue(new StandardEvaluationContext(root));
    }
}

7.5 Validate Expressions

Validate SpEL expressions during development to catch errors early.

8. Testing

Testing SpEL expressions ensures they evaluate correctly.

8.1 Unit Testing Expressions

@ExtendWith(MockitoExtension.class)
class SpelExpressionTest {
    private ExpressionParser parser = new SpelExpressionParser();
    
    @Test
    void testSimpleExpression() {
        Expression exp = parser.parseExpression("10 + 5");
        Integer result = exp.getValue(Integer.class);
        assertEquals(15, result);
    }
    
    @Test
    void testPropertyAccess() {
        User user = new User("John", "Doe", 30);
        Expression exp = parser.parseExpression("name");
        String name = exp.getValue(user, String.class);
        assertEquals("John", name);
    }
    
    @Test
    void testMethodCall() {
        Expression exp = parser.parseExpression("getName()");
        User user = new User("John", "Doe", 30);
        String name = exp.getValue(user, String.class);
        assertEquals("John", name);
    }
    
    @Test
    void testConditional() {
        Expression exp = parser.parseExpression("age >= 18 ? 'Adult' : 'Minor'");
        User user = new User("John", "Doe", 30);
        String category = exp.getValue(user, String.class);
        assertEquals("Adult", category);
    }
}

8.2 Integration Testing

@SpringJUnitConfig(AppConfig.class)
class SpelIntegrationTest {
    @Autowired
    private ApplicationContext context;
    
    @Test
    void testValueInjection() {
        SpelExample example = context.getBean(SpelExample.class);
        assertNotNull(example.getNumber());
        assertNotNull(example.getSystemUser());
    }
    
    @Test
    void testBeanReference() {
        SpelExample example = context.getBean(SpelExample.class);
        assertNotNull(example.getUserService());
    }
}

9. Advanced Concepts

9.1 Custom Functions

Register custom functions for use in SpEL expressions:

public class MathUtils {
    public static double square(double value) {
        return value * value;
    }
    
    public static boolean isEven(int value) {
        return value % 2 == 0;
    }
}

@Configuration
public class SpelConfig {
    @Bean
    public StandardEvaluationContext evaluationContext() {
        StandardEvaluationContext context = new StandardEvaluationContext();
        try {
            context.registerFunction("square", 
                MathUtils.class.getMethod("square", double.class));
            context.registerFunction("isEven",
                MathUtils.class.getMethod("isEven", int.class));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return context;
    }
}

// Usage
@Value("#{#square(10)}")  // 100
private double squared;

@Value("#{#isEven(10)}")  // true
private boolean even;

9.2 Type Conversion

SpEL automatically handles type conversion:

@Value("#{100}")           // int
private Integer number;

@Value("#{'100'}")         // String converted to int
private int numberFromString;

@Value("#{true}")          // boolean
private Boolean flag;

@Value("#{'true'}")        // String converted to boolean
private boolean flagFromString;

9.3 Template Expressions

SpEL supports template expressions for mixing literal text with expressions:

@Value("#{'Hello, ' + user.name + '!'}")
private String greeting;

@Value("#{user.name + ' is ' + user.age + ' years old'}")
private String userInfo;

// Using template parser
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("Hello, #{name}!", 
    new TemplateParserContext());
String result = exp.getValue(new StandardEvaluationContext(user), String.class);

9.4 Variable Assignment

SpEL supports variable assignment in expressions:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

// Set variable
context.setVariable("x", 10);
context.setVariable("y", 20);

// Use variable
Expression exp = parser.parseExpression("#x + #y");
Integer result = exp.getValue(context, Integer.class); // 30

10. Conclusion

Spring Expression Language (SpEL) is a powerful and flexible expression language that provides dynamic evaluation capabilities throughout Spring applications. It enables runtime expression evaluation, property access, method invocation, and complex conditional logic.

By mastering SpEL syntax, operators, collection operations, and integration patterns, you can create more flexible and dynamic Spring configurations. SpEL's integration with Spring annotations, XML configuration, and programmatic evaluation makes it a versatile tool for various use cases.

Whether you're using SpEL for dynamic configuration, conditional bean creation, caching keys, security expressions, or collection operations, understanding its capabilities helps you build more flexible and maintainable Spring applications.

Post a Comment

0 Comments