Spring Expression Language
The Spring Expression Language (SpEL) is a small expression syntax that Spring evaluates at runtime. It powers the #{ ... } expressions you see in @Value, the condition attribute of @EventListener and @Cacheable, and security annotations like @PreAuthorize. SpEL can read properties, call methods, navigate collections, and reference other beans — all from a string.
Placeholders vs expressions
The single most important distinction in @Value is the prefix:
${ ... }is a property placeholder — resolved fromapplication.properties, environment variables, or system properties before SpEL runs.#{ ... }is a SpEL expression — evaluated by the expression engine.
@Component
public class AppInfo {
// Property placeholder: reads app.name from configuration
@Value("${app.name}")
private String name;
// SpEL expression: computed at runtime
@Value("#{ 2 * 60 * 1000 }")
private long cacheTtlMillis;
// Combine them — placeholder resolved first, then SpEL evaluates
@Value("#{'${app.name}'.toUpperCase()}")
private String upperName;
}
Warning: A placeholder inside a SpEL block produces raw text, so
#{${app.name}}fails unless the value is a number. Quote it —#{'${app.name}'}— so SpEL sees a string literal.
Literals and operators
SpEL supports the literals and operators you would expect from a scripting language.
@Value("#{ 'hello' }") private String text; // string literal
@Value("#{ 3.14 }") private double pi; // number
@Value("#{ true }") private boolean enabled; // boolean
@Value("#{ 10 > 5 and 1 < 2 }") private boolean check; // logical
| Operator | Example | Meaning |
|---|---|---|
| Arithmetic | #{ 2 + 3 * 4 } | Standard math |
| Relational | #{ count > 10 }, #{ a eq b } | gt lt ge le eq ne also work |
| Logical | #{ a and b }, #{ not x } | and or not |
| Ternary | #{ active ? 'on' : 'off' } | Conditional value |
| Elvis | #{ name ?: 'guest' } | Value if non-null, else fallback |
| Safe navigation | #{ user?.address?.city } | Returns null instead of NPE |
| Type | #{ T(java.lang.Math).PI } | Static access via T() |
| Bean reference | #{ @clock.now() } | Reference another bean |
Referencing properties with the Elvis operator
The Elvis operator pairs naturally with placeholders to supply defaults, though plain placeholders also support a :default syntax.
// SpEL Elvis: fall back when the resolved value is null/empty
@Value("#{ '${api.timeout:}' ?: '30' }")
private String timeout;
// Equivalent using the placeholder default syntax
@Value("${api.timeout:30}")
private String timeoutSimple;
Method calls and type operator
SpEL can invoke methods and access static members through the T() type operator, which resolves a java.lang.Class.
@Value("#{ T(java.time.LocalDate).now().getYear() }")
private int currentYear;
@Value("#{ '${app.name}'.length() }")
private int nameLength;
@Value("#{ T(java.lang.Math).max(10, 20) }")
private int maxValue;
Referencing other beans
Prefix a bean name with @ to inject one bean’s property or method result into another.
@Component("clock")
public class SystemClock {
public String zone() { return "UTC"; }
}
@Component
public class Report {
@Value("#{ @clock.zone() }")
private String timezone; // resolves to "UTC"
}
Collection selection and projection
Two of SpEL’s most useful features operate on collections and maps.
Selection .?[ ... ] filters a collection, keeping elements for which the expression is true. Inside the brackets, #this is the current element.
Projection .![ ... ] transforms each element into a new collection.
@Component
public class Catalog {
// Keep only prices above 100
@Value("#{ ${prices} .?[ #this > 100 ] }")
private List<Integer> expensive;
// Double every price
@Value("#{ ${prices} .![ #this * 2 ] }")
private List<Integer> doubled;
}
With prices=50,150,200 in configuration:
expensive = [150, 200]
doubled = [100, 300, 400]
For maps, #this is a Map.Entry, so you can select on #this.key and #this.value, and .?[true] returns the matching entries.
Standalone parsing with ExpressionParser
SpEL also works outside the container. The ExpressionParser API lets you evaluate expressions against any object, which is handy for rule engines or dynamic queries.
ExpressionParser parser = new SpelExpressionParser();
// Literal expression
int result = parser.parseExpression("100 * 2 + 5").getValue(Integer.class);
System.out.println(result); // 205
// Against a root object
record User(String name, boolean active) { }
StandardEvaluationContext ctx = new StandardEvaluationContext(new User("Mia", true));
String greeting = parser.parseExpression("active ? 'Hi ' + name : 'inactive'")
.getValue(ctx, String.class);
System.out.println(greeting); // Hi Mia
Output:
205
Hi Mia
Tip: When parsing untrusted input, use a restricted
SimpleEvaluationContextinstead ofStandardEvaluationContextto disable type references and bean access —T()and@beanaccess can be a security risk.
Best Practices
- Remember
${...}resolves configuration;#{...}evaluates SpEL. Don’t mix them blindly. - Quote placeholders used inside SpEL string operations:
#{'${app.name}'.trim()}. - Prefer the placeholder default syntax
${key:default}for simple fallbacks; reserve SpEL for real computation. - Keep
@Valueexpressions short — move complex logic into@ConfigurationPropertiesor a bean. - Use
SimpleEvaluationContextwhen evaluating expressions from external input.