Skip to content
Spring Boot sb core 4 min read

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 from application.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
OperatorExampleMeaning
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 SimpleEvaluationContext instead of StandardEvaluationContext to disable type references and bean access — T() and @bean access 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 @Value expressions short — move complex logic into @ConfigurationProperties or a bean.
  • Use SimpleEvaluationContext when evaluating expressions from external input.
Last updated June 13, 2026
Was this helpful?