Spring – [ SpEL ]

Have you ever stopped to wonder how Spring makes real-time decisions?

If you’ve ever secured a method based on a user’s role, filtered a database query dynamically, or conditionally cached a heavy result, you weren’t just using static configuration.

You were using the Spring Expression Language (SpEL).

SpEL is the “invisible hand” that makes the Spring ecosystem intelligent.

It’s the engine running under the hood of your favorite annotations :

  • Spring Security (Deciding access on the fly) :
    @PreAuthorize(“hasRole(‘ADMIN’) and #user.id == principal.id”)
  • Spring Data (Mapping objects directly into JPQL) :
    @Query(“select u from User u where u.firstname = :#{#customer.firstname}”)
  • Spring Cache (Logic-based performance tuning.) :
    @Cacheable(value=”users”, condition=”#result.size() > 10″)

1. What is SpEL ?

At its core, Spring Expression Language (SpEL) is a powerful expression language that allows you to query and manipulate an object graph at runtime.

Think of it as a “mini-language” embedded within Java.

While standard Java code is static and compiled, SpEL expressions are evaluated dynamically as your application runs.

This gives you the flexibility to change your application’s behavior based on:

  • The current state of other Beans in the context;
  • System environment variables;
  • The results of method calls.

The “Big Three” of the SpEL Engine

To understand how SpEL works without the “magic” of annotations, you only need to know three components :

  • ExpressionParser (The “Interpreter”) :
    It takes a raw String (like ‘Hello’ + ‘ World’) and parses it into an expression object.
  • Expression (The “Logic”) :
    This represents the parsed tree of your command, ready to be executed.
  • EvaluationContext (The “Environment”) :
    This is the sandbox where the expression lives and where SpEL looks to find the variables, objects, or beans you are talking about.
package com.example.spel;

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

public class SpELExample1 {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("greeting", "Hello World from SpEL");

        Expression exp = parser.parseExpression("#greeting");

        String value = exp.getValue(context, String.class);

        System.out.println(value);
    }
}
/**
    Output:
        Hello World from SpEL
*/

2. How SpEL works ?

2.1 Literal Expressions

SpEL supports the following types of literal expressions : String, Number, Boolean, and Null.

package com.example.spel;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample2 {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        System.out.println(parser.parseExpression("'Hello World'").getValue());
        System.out.println(parser.parseExpression("'Tony''s Pizza'").getValue());
        System.out.println(parser.parseExpression("6.0221415E+23").getValue());
        System.out.println(parser.parseExpression("0x7FFFFFFF").getValue());
        System.out.println(parser.parseExpression("true").getValue());
        System.out.println(parser.parseExpression("null").getValue());
    }
}
/**
 * Output:
 * Hello World
 * Tony's Pizza
 * 6.0221415E+23
 * 2147483647
 * true
 * null
 */

2.2 Operators

SpEL supports the following kinds of operators : Relational, Logical, Mathematical, and String.

package com.example.spel;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample3 {
    private static final ExpressionParser parser = new SpelExpressionParser();

    public static void main(String[] args) {
        print("10 > 5");
        print("10 < 5");
        print("10 >= 5");
        print("10 <= 5");
        print("10 == 10");
        print("5 != 5");

        System.out.println();

        print("1 between {1, 5}");
        print("1 between {10, 15}");
        print("'elephant' between {'aardvark', 'zebra'}");
        print("'elephant' between {'aardvark', 'cobra'}");
        print("123 instanceof T(Integer)");
        print("'xyz' instanceof T(Integer)");
        print("'5.00' matches '^-?\\d+(\\.\\d{2})?$'");
        print("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'");

        System.out.println();

        print("true and false");
        print("true or false");
        print("not false");
        print("true && false");
        print("true || false");
        print("!false");

        System.out.println();

        print("10 + 5");
        print("20 - 5");
        print("10 * 5");
        print("7 % 4");
        print("2 ^ 31");

        System.out.println();

        print("'hello' + ' ' + 'world'");
        print("'d' - 3");
        print("'abc' * 2");
    }

    private static void print(String expression) {
        Object value = parser.parseExpression(expression).getValue();
        System.out.println(expression + " : " + value);
    }
}
/**
 * Output:
        10 > 5 : true
        10 < 5 : false
        10 >= 5 : true
        10 <= 5 : false
        10 == 10 : true
        5 != 5 : false
        
        1 between {1, 5} : true
        1 between {10, 15} : false
        'elephant' between {'aardvark', 'zebra'} : true
        'elephant' between {'aardvark', 'cobra'} : false
        123 instanceof T(Integer) : true
        'xyz' instanceof T(Integer) : false
        '5.00' matches '^-?\d+(\.\d{2})?$' : true
        '5.0067' matches '^-?\d+(\.\d{2})?$' : false
        
        true and false : false
        true or false : true
        not false : true
        true && false : false
        true || false : true
        !false : true
        
        10 + 5 : 15
        20 - 5 : 15
        10 * 5 : 50
        7 % 4 : 3
        2 ^ 31 : 2147483648
        
        'hello' + ' ' + 'world' : hello world
        'd' - 3 : a
        'abc' * 2 : abcabc
 */

2.3 Inline Lists

We can directly express lists in an expression by using {} notation.

package com.example.spel;

import java.util.List;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample4 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        @SuppressWarnings("unchecked")
        List<Integer> l1 = (List<Integer>) parser.parseExpression("{1,2,3,4}").getValue();

        l1.forEach(System.out::print);

        System.out.println();

        @SuppressWarnings("unchecked")
        List<List<String>> l2 = (List<List<String>>) parser.parseExpression("{{'a','b'},{'x','y'}}")
                .getValue();

        l2.forEach(l -> {
            l.forEach(System.out::print);
            System.out.println();
        });
    }
}
/**
 * Output:
 * 1234
 * ab
 * xy
 */

2.4 Inline Maps

We can directly express maps in an expression by using {key : value} notation.

package com.example.spel;

import java.util.Map;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample5 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        @SuppressWarnings("unchecked")
        Map<String, Object> m1 = (Map<String, Object>) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue();

        m1.forEach((k, v) -> System.out.println(k + " : " + v));

        System.out.println();

        @SuppressWarnings("unchecked")
        Map<String, Object> m2 = (Map<String, Object>) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue();

        m2.forEach((k, v) -> System.out.println(k + " : " + v));
    }
}
/**
 * Output:
 * name : Nikola
 * dob : 10-July-1856
 *
 * name : {first=Nikola, last=Tesla}
 * dob : {day=10, month=July, year=1856}
 */

2.5 Array Construction

We can build arrays by using the familiar Java syntax, optionally supplying an initializer to have the array populated at construction time.

package com.example.spel;

import java.util.Arrays;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample6 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        int[] arr1 = (int[]) parser.parseExpression("new int[4]").getValue();

        System.out.println(arr1.length);

        int[] arr2 = (int[]) parser.parseExpression("new int[] {1, 2, 3}").getValue();

        System.out.println(Arrays.toString(arr2));

        int[][] arr3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();

        System.out.println(arr3.length);
        System.out.println(arr3[0].length);

        // multidimensional array with initializer is not supported yet
        int[][] arr4 = (int[][]) parser.parseExpression("new int[][] {{1, 2}, {3, 4}}").getValue();

        Arrays.stream(arr4).forEach(a -> System.out.println(Arrays.toString(a)));
    }
}
/**
    Output:
        4
        [1, 2, 3]
        4
        5
        Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1062E: Using an initializer to build a multi-dimensional array is not currently supported
                at org.springframework.expression.spel.ast.ConstructorReference.createArray(ConstructorReference.java:323)
                at org.springframework.expression.spel.ast.ConstructorReference.getValueInternal(ConstructorReference.java:113)
                at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
                at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:140)
                at com.example.spel.SpELExample6.main(SpELExample6.java:25)
*/

2.6 Types

We can use the special T operator to specify an instance of java.lang.Class (the type).

Static methods are invoked by using this operator as well.

package com.example.spel;

import java.util.Date;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample7 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        @SuppressWarnings("unchecked")
        Class<Date> d = (Class<Date>) parser.parseExpression("T(java.util.Date)").getValue();

        System.out.println(d);

        @SuppressWarnings("unchecked")
        Class<String> s = (Class<String>) parser.parseExpression("T(String)").getValue();

        System.out.println(s);

        Double r = (Double) parser.parseExpression("T(java.lang.Math).random()").getValue();

        System.out.println(r);
    }
}
/**
    Output:
        class java.util.Date
        class java.lang.String
        0.12345678901234567
*/

2.7 Accessing Object Properties

We can access properties of objects.

package com.example.spel;

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

public class SpELExample8 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        Person tom = new Person("tom", 18);

        ExpressionParser parser = new SpelExpressionParser();

        String name = parser.parseExpression("name").getValue(tom, String.class);
        int age = parser.parseExpression("age").getValue(tom, Integer.class);

        System.out.println("Name: " + name);
        System.out.println("Age: " + age);

        System.out.println();

        StandardEvaluationContext context = new StandardEvaluationContext(tom);

        String name2 = parser.parseExpression("name").getValue(context, String.class);
        int age2 = parser.parseExpression("age").getValue(context, Integer.class);

        System.out.println("Name: " + name2);
        System.out.println("Age: " + age2);

        System.out.println();

        StandardEvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("tom", tom);

        String name3 = parser.parseExpression("#tom.name").getValue(context2, String.class);
        int age3 = parser.parseExpression("#tom.age").getValue(context2, Integer.class);

        System.out.println("Name: " + name3);
        System.out.println("Age: " + age3);
    }
}
/**
    Output:
        Name: tom
        Age: 18

        Name: tom
        Age: 18

        Name: tom
        Age: 18
*/

2.8 Method Invocation on Objects

We can invoke methods on objects by using the typical Java programming syntax.

package com.example.spel;

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

public class SpELExample9 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        Person tom = new Person("tom", 18);

        ExpressionParser parser = new SpelExpressionParser();

        String name = parser.parseExpression("name()").getValue(tom, String.class);
        int age = parser.parseExpression("age()").getValue(tom, Integer.class);

        System.out.println("Name: " + name);
        System.out.println("Age: " + age);

        System.out.println();

        StandardEvaluationContext context = new StandardEvaluationContext(tom);

        String name2 = parser.parseExpression("name()").getValue(context, String.class);
        int age2 = parser.parseExpression("age()").getValue(context, Integer.class);

        System.out.println("Name: " + name2);
        System.out.println("Age: " + age2);

        System.out.println();

        StandardEvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("tom", tom);

        String name3 = parser.parseExpression("#tom.name()").getValue(context2, String.class);
        int age3 = parser.parseExpression("#tom.age()").getValue(context2, Integer.class);

        System.out.println("Name: " + name3);
        System.out.println("Age: " + age3);

        System.out.println();

        System.out.println(parser.parseExpression("'hello'.toUpperCase()").getValue());
        System.out.println(parser.parseExpression("'WORLD'.toLowerCase()").getValue());
    }
}
/**
    Output:
        Name: tom
        Age: 18

        Name: tom
        Age: 18

        Name: tom
        Age: 18

        HELLO
        world
*/

2.9 Constructors

We can invoke constructors to create new objects by using the new operator, just like in Java.

We should use the fully qualified class name for all types except those located in the java.lang package (Integer, Float, String, and so on).

package com.example.spel;

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

public class SpELExample10 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        String className = Person.class.getName();

        Expression expression = parser.parseExpression("new " + className + "('tom', 18)");

        Person tom = expression.getValue(Person.class);

        System.out.println(tom);

        System.out.println();

        StandardEvaluationContext context = new StandardEvaluationContext();
        var constructor = Person.class.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        context.setVariable("personCtor", constructor);

        expression = parser.parseExpression("#personCtor.newInstance('tom', 18)");

        tom = (Person) expression.getValue(context);

        System.out.println(tom);
    }

    public record Person(String name, int age) {
    }
}
/**
    Output:
        Person[name=tom, age=18]

        Person[name=tom, age=18]
 */

2.10 Indexing into Arrays and Collections

The nth element of an array or collection (for example, a Set or List) can be obtained by using square bracket notation.

package com.example.spel;

import java.util.List;

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

public class SpELExample11 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        int[] arr = { 1, 2, 3 };

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("arr", arr);

        Expression expression = parser.parseExpression("#arr[0] + #arr[1] + #arr[2]");

        int result = expression.getValue(context, Integer.class);

        System.out.println(result);

        System.out.println();

        List<Integer> list = List.of(1, 2, 3);
        context.setVariable("list", list);

        expression = parser.parseExpression("#list[0] + #list[1] + #list[2]");

        result = expression.getValue(context, Integer.class);

        System.out.println(result);
    }
}
/**
 * Output:
 * 6
 * 6
 */

2.11 Indexing into Strings

The nth character of a string can be obtained by specifying the index within square brackets.

package com.example.spel;

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

public class SpELExample12 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        String greeting = "hello world";

        for (int i = 0; i < greeting.length(); i++) {
            Expression expression = parser.parseExpression("'" + greeting + "'[" + i + "]");

            String result = expression.getValue(String.class);

            System.out.print(result);
        }

        System.out.println();
    }
}
/**
 * Output:
 * hello world
 */

2.12 Indexing into Maps

The contents of maps are obtained by specifying the key value within square brackets.

package com.example.spel;

import java.util.Map;

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

public class SpELExample13 {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

        Map<String, Integer> m = Map.of("a", 1, "b", 2, "c", 3);

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("m", m);

        m.keySet().forEach(key -> {
            Expression expression = parser.parseExpression("#m['" + key + "']");

            Integer value = expression.getValue(context, Integer.class);

            System.out.println(key + " : " + value);
        });
    }
}
/**
 * Output:
 * a : 1
 * b : 2
 * c : 3
 */

2.13 Indexing into Objects

A property of an object can be obtained by specifying the name of the property within square brackets.

This is analogous to accessing the value of a map based on its key.

package com.example.spel;

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

public class SpELExample14 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        Person tom = new Person("tom", 18);

        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("tom", tom);

        Expression expression = parser.parseExpression("#tom['name']");
        String name = expression.getValue(context, String.class);
        System.out.println(name);

        expression = parser.parseExpression("#tom['age']");
        int age = expression.getValue(context, Integer.class);
        System.out.println(age);
    }
}
/**
 * Output:
 * tom
 * 18
 */

2.14 Variables

We can reference variables in an expression by using the #variableName syntax.

Variables are set by using the setVariable() method in EvaluationContext implementations.

package com.example.spel;

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

public class SpELExample15 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        Person tom = new Person("tom", 18);

        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("tom", tom);

        Expression expression = parser.parseExpression("#tom.name + ' is ' + #tom.age + ' years old.'");
        System.out.println(expression.getValue(context, String.class));
    }
}
/**
 * Output:
 * tom is 18 years old.
 */

The #this variable is always defined and refers to the current evaluation object.

package com.example.spel;

import java.util.List;

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

public class SpELExample16 {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("numbers", numbers);

        Expression expression = parser.parseExpression("#numbers.?[#this % 2 == 0]");

        System.out.println(expression.getValue(context, List.class));
    }
}
/**
 * Output:
 * [0, 2, 4, 6, 8]
 */

The #root variable is always defined and refers to the root context object.

Although #this may vary as components of an expression are evaluated, #root always refers to the root.

package com.example.spel;

import java.util.List;

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

public class SpELExample17 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        List<Person> people = List.of(
                new Person("tom", 18),
                new Person("jerry", 16));

        ExpressionParser parser = new SpelExpressionParser();

        Expression expression = parser.parseExpression("#root.?[#this.age > 17]");

        System.out.println(expression.getValue(people, List.class));

        System.out.println();

        StandardEvaluationContext context = new StandardEvaluationContext(people);

        expression = parser.parseExpression("#root.?[#this.age < 17]");

        System.out.println(expression.getValue(context, List.class));
    }
}
/**
 * Output:
 * [Person[name=tom, age=18]]
 *
 * [Person[name=jerry, age=16]]
 */

2.15 Functions

We can extend SpEL by registering user-defined functions that can be called within expressions by using the #functionName(…​) syntax.

SpEL only supports static methods for functions.

package com.example.spel;

import java.lang.reflect.Method;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELExample18 {
    public static String reverse(String input) {
        return new StringBuilder(input).reverse().toString();
    }

    public static void main(String[] args) throws NoSuchMethodException {
        StandardEvaluationContext context = new StandardEvaluationContext();

        Method method = SpELExample18.class.getDeclaredMethod("reverse", String.class);

        context.registerFunction("reverse", method);

        SpelExpressionParser parser = new SpelExpressionParser();

        String result = parser.parseExpression("#reverse('hello world')").getValue(context, String.class);

        System.out.println(result);
    }
}
/**
 * Output:
 * dlrow olleh
 */

2.16 Varargs Invocations

The Spring Expression Language supports varargs invocations for constructors, methods, and user-defined functions.

package com.example.spel;

import java.lang.reflect.Method;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELExample19 {
    public static int sum(int... numbers) {
        int total = 0;
        for (int n : numbers) {
            total += n;
        }
        return total;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        StandardEvaluationContext context = new StandardEvaluationContext();

        Method method = SpELExample19.class.getDeclaredMethod("sum", int[].class);

        context.registerFunction("sum", method);

        SpelExpressionParser parser = new SpelExpressionParser();

        int total = parser.parseExpression("#sum(1, 2, 3, 4)").getValue(context, Integer.class);

        System.out.println(total);
    }
}
/**
 * Output:
 * 10
 */

2.17 Ternary Operator (If-Then-Else)

We can use the ternary operator for performing if-then-else conditional logic inside the expression.

package com.example.spel;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample20 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        Person tom = new Person("tom", 18);

        ExpressionParser parser = new SpelExpressionParser();

        String result = parser.parseExpression(
                "age >= 18 ? 'Adult' : 'Minor'").getValue(tom, String.class);

        System.out.println(result);
    }
}
/*
 * Output:
 * Adult
 */

2.18 The Elvis Operator

We can use the Elvis operator (?:) which is a shortening of the ternary operator syntax.

package com.example.spel;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample21 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        ExpressionParser parser = new SpelExpressionParser();

        String result = parser.parseExpression("name ?: 'Anonymous'").getValue(new Person(null, 18), String.class);

        System.out.println(result);

        System.out.println();

        System.out.println(parser.parseExpression("name ?: 'Anonymous'").getValue(new Person("tom", 18), String.class));

        System.out.println();

        System.out.println(parser.parseExpression("name != null ? name : 'Anonymous'").getValue(new Person(null, 18),
                String.class));

        System.out.println();

        System.out.println(parser.parseExpression("name != null ? name : 'Anonymous'").getValue(new Person("tom", 18),
                String.class));
    }
}
/*
 * Output:
 * Anonymous
 *
 * tom
 *
 * Anonymous
 *
 * tom
 */

2.19 Safe Navigation Operator

We can use the safe navigation operator (?.) to avoid a NullPointerException.

Typically, when we have a reference to an object, we might need to verify that it is not null before accessing methods or properties of the object.

To avoid this, the safe navigation operator returns null for the particular null-safe operation instead of throwing an exception.

package com.example.spel;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELExample22 {
    public static void main(String[] args) {
        record Address(String city, String country) {
        }

        record Person(String name, int age, Address address) {
        }

        Person tom = new Person("tom", 18, null);

        ExpressionParser parser = new SpelExpressionParser();

        try {
            System.out.println(parser.parseExpression("address.city").getValue(tom, String.class));
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }

        System.out.println();

        System.out.println(parser.parseExpression("address?.city").getValue(tom, String.class));

        System.out.println();

        // Combining safe navigation operator with Elvis operator
        System.out.println(parser.parseExpression("address?.city ?: 'Unknown'").getValue(tom, String.class));
    }
}
/*
 * Output:
 * Error: EL1007E: Property or field 'city' cannot be found on null
 *
 * null
 *
 * Unknown
 */

2.20 Collection Selection

Selection is a powerful expression language feature that lets you transform a source collection into another collection by selecting from its entries.

Selection uses a syntax of .?[selectionExpression].

It filters the collection and returns a new collection that contains a subset of the original elements.

package com.example.spel;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

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

public class SpELExample23 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 15, 20, 25, 30);

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("numbers", numbers);

        ExpressionParser parser = new SpelExpressionParser();

        System.out.println(parser.parseExpression("#numbers.?[#this > 15]").getValue(context, List.class));
        // First Match
        System.out.println(parser.parseExpression("#numbers.^[#this > 15]").getValue(context, Integer.class));
        // Last Match
        System.out.println(parser.parseExpression("#numbers.$[#this > 15]").getValue(context, Integer.class));

        System.out.println();

        record Person(String name, int age) {
        }

        List<Person> people = Arrays.asList(
                new Person("tom", 18),
                new Person("jerry", 16));

        context.setVariable("people", people);

        System.out.println(parser.parseExpression("#people.?[age >= 18]").getValue(context, List.class));
        System.out.println(parser.parseExpression("#people.?[#this.age < 18]").getValue(context, List.class));

        System.out.println();

        Map<String, Integer> scores = Map.of(
                "tom", 60,
                "jerry", 85);

        context.setVariable("scores", scores);

        System.out.println(parser.parseExpression("#scores.?[value >= 80]").getValue(context, Map.class));
    }
}
/*
 * Output:
 * [20, 25, 30]
 * 20
 * 30
 *
 * [Person[name=tom, age=18]]
 * [Person[name=jerry, age=16]]
 *
 * {jerry=85}
 */

2.21 Collection Projection

Projection lets a collection drive the evaluation of a sub-expression, and the result is a new collection.

The syntax for projection is .![projectionExpression].

package com.example.spel;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

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

public class SpELExample24 {
    public static void main(String[] args) {
        record Person(String name, int age) {
        }

        List<Person> people = Arrays.asList(
                new Person("tom", 18),
                new Person("jerry", 16));

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("people", people);

        ExpressionParser parser = new SpelExpressionParser();

        System.out.println(parser.parseExpression("#people.![name]").getValue(context, List.class));
        System.out.println(parser.parseExpression("#people.![#this.name]").getValue(context, List.class));

        System.out.println();

        System.out.println(parser.parseExpression("#people.![age + 10]").getValue(context, List.class));

        System.out.println();

        System.out.println(parser.parseExpression("#people.![name.toUpperCase()]").getValue(context, List.class));

        System.out.println();

        Map<String, Integer> scores = Map.of(
                "tom", 60,
                "jerry", 85);

        context.setVariable("scores", scores);

        System.out.println(parser.parseExpression("#scores.![key + \":\" + value]").getValue(context, List.class));

        System.out.println();

        // Selection Combining with Projection
        System.out.println(parser.parseExpression("#people.?[age >= 18].![name]").getValue(context, List.class));

        System.out.println();

        List<List<Integer>> matrix = Arrays.asList(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5, 6));

        context.setVariable("matrix", matrix);

        // Nested Projection
        System.out.println(parser.parseExpression("#matrix.![#this.![#this * 2]]").getValue(context, List.class));
    }
}
/*
 * Output:
 * [tom, jerry]
 * [tom, jerry]
 *
 * [28, 26]
 *
 * [TOM, JERRY]
 *
 * [tom:60, jerry:85]
 *
 * [tom]
 *
 * [[2, 4, 6], [8, 10, 12]]
 */

2.22 Bean References

Bean references allow us to reference other beans in the Spring container.

If the evaluation context has been configured with a bean resolver, we can look up beans from an expression by using the @ symbol as a prefix.

package com.example.spel;

import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELExample25 {
        public static void main(String[] args) {
                ExpressionParser parser = new SpelExpressionParser();

                StandardEvaluationContext context = new StandardEvaluationContext();
                context.setBeanResolver(new MyBeanResolver());

                System.out.println(parser.parseExpression("@person").getValue(context, Person.class));

                System.out.println();

                System.out.println(parser.parseExpression("@person.greeting()").getValue(context, String.class));
        }

        private static class Person {
                private final String name;
                private final int age;

                public Person(String name, int age) {
                        this.name = name;
                        this.age = age;
                }

                public String greeting() {
                        return "Hello, I'm " + name + " and I'm " + age + " years old.";
                }

                @Override
                public String toString() {
                        return "Person{name='" + name + "', age=" + age + "}";
                }
        }

        private static class MyBeanResolver implements BeanResolver {
                @Override
                public Object resolve(EvaluationContext context, String beanName) throws AccessException {
                        return switch (beanName) {
                                case "person" -> new Person("tom", 18);
                                default -> throw new AccessException("No such bean: " + beanName);
                        };
                }
        }
}
/*
 * Output:
 * Person{name='tom', age=18}
 * 
 * Hello, I'm tom and I'm 18 years old.
 */

To access a factory bean itself, we should instead prefix the bean name with an & symbol.

Pay attention that when we use SpEL outside Spring’s BeanFactory, the & prefix has no built-in meaning.

&xxx is just a string passed to our BeanResolver.

So we must implement the & behavior ourself.

package com.example.spel;

import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELExample26 {
        public static void main(String[] args) throws Exception {
                ExpressionParser parser = new SpelExpressionParser();

                StandardEvaluationContext context = new StandardEvaluationContext();
                context.setBeanResolver(new MyBeanResolver());

                MyFactoryBean factory = parser.parseExpression("&myFactoryBean").getValue(context, MyFactoryBean.class);

                System.out.println(factory.getObject());
        }

        private static class MyFactoryBean implements FactoryBean<String> {
                @Override
                public @Nullable String getObject() throws Exception {
                        return "Hello World from MyFactoryBean";
                }

                @Override
                public @Nullable Class<String> getObjectType() {
                        return String.class;
                }

        }

        private static class MyBeanResolver implements BeanResolver {
                @Override
                public Object resolve(EvaluationContext context, String beanName) throws AccessException {
                        return switch (beanName) {
                                case "&myFactoryBean" -> new MyFactoryBean();
                                default -> throw new AccessException("No such bean: " + beanName);
                        };
                }
        }
}
/**
 * Output :
 *      Hello World from MyFactoryBean
 */

2.23 Expression Templating

Expression templates allow mixing literal text with one or more evaluation blocks.

Each evaluation block is delimited with prefix and suffix characters that you can define.

A common choice is to use #{ } as the delimiters.

package com.example.spel;

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

public class SpELExample27 {

        public static void main(String[] args) {
                ExpressionParser parser = new SpelExpressionParser();

                StandardEvaluationContext context = new StandardEvaluationContext();
                context.setVariable("name", "john");
                context.setVariable("age", 25);

                Expression expression = parser.parseExpression(
                                "Hello #{#name.toUpperCase()}, you are #{#age} years old!",
                                new TemplateParserContext());

                String result = expression.getValue(context, String.class);

                System.out.println(result);

                System.out.println();

                // Custom Delimiters
                ParserContext customParserContext = new ParserContext() {
                        @Override
                        public String getExpressionPrefix() {
                                return "<<";
                        }

                        @Override
                        public String getExpressionSuffix() {
                                return ">>";
                        }

                        @Override
                        public boolean isTemplate() {
                                return true;
                        }
                };

                expression = parser.parseExpression("Hello <<#name.toUpperCase()>>, you are <<#age>> years old!",
                                customParserContext);

                result = expression.getValue(context, String.class);

                System.out.println(result);
        }
}
/**
 * Output :
 *      Hello JOHN, you are 25 years old!
 *
 *      Hello JOHN, you are 25 years old!
 */

2.24 Overloaded Operators

By default, the mathematical operations (ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS, and POWER) support simple types like numbers.

By providing an implementation of OperatorOverloader, the expression language can support these operations on other types.

For example, if we want to overload the ADD operator to allow two lists to be concatenated using the + sign, we can implement a custom OperatorOverloader as follows.

package com.example.spel;

import java.util.ArrayList;
import java.util.List;

import org.jspecify.annotations.Nullable;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELExample28 {

        public static void main(String[] args) {
                ExpressionParser parser = new SpelExpressionParser();

                StandardEvaluationContext context = new StandardEvaluationContext();
                context.setOperatorOverloader(new ListConcatenationOverloader());

                List<String> list1 = List.of("A", "B", "C");
                List<String> list2 = List.of("D", "E", "F");

                context.setVariable("list1", list1);
                context.setVariable("list2", list2);

                Expression expression = parser.parseExpression("#list1 + #list2");

                List<?> result = expression.getValue(context, List.class);

                System.out.println(result);
        }

        private static class ListConcatenationOverloader implements OperatorOverloader {

                @Override
                public boolean overridesOperation(Operation operation, @Nullable Object leftOperand,
                                @Nullable Object rightOperand) throws EvaluationException {
                        // We only override ADD (+) for Lists
                        return operation == Operation.ADD
                                        && leftOperand instanceof List
                                        && rightOperand instanceof List;
                }

                @Override
                public Object operate(Operation operation, @Nullable Object leftOperand, @Nullable Object rightOperand)
                                throws EvaluationException {
                        if (operation == Operation.ADD) {
                                List<?> leftList = (List<?>) leftOperand;
                                List<?> rightList = (List<?>) rightOperand;

                                List<Object> result = new ArrayList<>();
                                result.addAll(leftList);
                                result.addAll(rightList);

                                return result;
                        }

                        throw new UnsupportedOperationException("Operation not supported");
                }
        }
}
/**
 * Output :
 *      [A, B, C, D, E, F]
 */

2.25 SpEL Compiler Modes

The SpEL Compiler (Spring Expression Language Compiler) is an optimization feature in Spring that turns interpreted SpEL expressions into bytecode at runtime.

This can significantly improve performance when expressions are evaluated repeatedly.

The SpEL Compiler has three modes :

  • OFF : SpEL expressions are always interpreted (default);
  • IMMEDIATE : SpEL expressions are compiled immediately upon first evaluation;
  • MIXED : SpEL expressions are interpreted until they are evaluated a certain number of times, at which point they are compiled (recommended for production).

Below is a benchmark example that compares the 3 modes by measuring execution time.

package com.example.spel;

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

public class SpELExample29 {

        public static void main(String[] args) {
                int iterations = 1_000_000;

                System.out.println("Running benchmark with " + iterations + " iterations...\n");

                testMode("INTERPRETED (OFF)", SpelCompilerMode.OFF, iterations);
                testMode("IMMEDIATE", SpelCompilerMode.IMMEDIATE, iterations);
                testMode("MIXED", SpelCompilerMode.MIXED, iterations);
        }

        private static void testMode(String label, SpelCompilerMode mode, int iterations) {
                record Person(String name, int age) {
                }

                SpelParserConfiguration config = new SpelParserConfiguration(mode,
                                SpELExample29.class.getClassLoader());

                ExpressionParser parser = new SpelExpressionParser(config);

                // Expression with method call + property access
                Expression exp = parser.parseExpression("name.toUpperCase() + ' - ' + age");

                Person person = new Person("john", 30);
                StandardEvaluationContext context = new StandardEvaluationContext(person);

                // Warm-up (important for MIXED mode to trigger compilation)
                for (int i = 0; i < 10_000; i++) {
                        exp.getValue(context);
                }

                long start = System.nanoTime();

                for (int i = 0; i < iterations; i++) {
                        exp.getValue(context);
                }

                long end = System.nanoTime();

                long durationMs = (end - start) / 1_000_000;

                System.out.println(label + " => " + durationMs + " ms");
        }
}
/**
 * Output (will vary based on machine and JVM optimizations):
        Running benchmark with 1000000 iterations...

        INTERPRETED (OFF) => 1190 ms
        IMMEDIATE => 982 ms
        MIXED => 795 ms
 */

3. Common SpEL Mistakes and Pitfalls

3.1 Overly Complex Expressions

SpEL’s power can be a double-edged sword – it’s easy to write expressions that are so complex they become unreadable and unmaintainable.

Bad Example :

parser.parseExpression("orders.?[price > 100].![price * taxRate].sum()");

Recommendation :

Break down complex expressions into simpler ones, or move complex logic into helper methods that can be called from SpEL.

Move logic into helper methods :

double calculateTax(List<Order> orders)

Then call this method from SpEL :

parser.parseExpression("@orderService.calculateTax(orders)");

3.2 Hidden Runtime Errors

SpEL expressions are evaluated at runtime, which means that any errors in the expression (syntax errors, type mismatches, null pointer exceptions, etc.) will only surface when the expression is actually evaluated and not at compile time.

Example :

parser.parseExpression("user.agge");

Above compiles fine, but at runtime, it will throw a SpelEvaluationException because there is no property named “agge” on the user object.

Best Practice :

Extract expressions into constants or configuration properties :

public class Expressions { public static final String AGE = "user.age"; }

And unit test them thoroughly to catch any errors before they reach production.

@Test

void shouldFailOnInvalidProperty() {

        ExpressionParser parser = new SpelExpressionParser();

        assertThrows(SpelEvaluationException.class, () -> {

                Expression exp = parser.parseExpression("T(com.example.Expressions).AGE");

                exp.getValue(new StandardEvaluationContext(new User("tom", 18)));

        });

}

Use extracted expressions in SpEL :

parser.parseExpression("T(com.example.Expressions).AGE");

3.3 Performance Issues

Parsing the SpEL expression repeatedly inside a loop can lead to performance issues, especially if the expression is complex.

package com.example.spel;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

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

public class SpELExample30 {
        public static void main(String[] args) {
                ExpressionParser parser = new SpelExpressionParser();

                String expression = "age > 18";

                List<User> users = generateUsers(1_000_000);

                bad(parser, users, expression);

                System.out.println();

                good(parser, users, expression);
        }

        private static void good(ExpressionParser parser, List<User> users, String expression) {
                // ✅ parse once
                Expression exp = parser.parseExpression(expression);

                long start = System.currentTimeMillis();

                int count = 0;

                StandardEvaluationContext context = new StandardEvaluationContext();

                for (User user : users) {
                        context.setRootObject(user);

                        Boolean result = exp.getValue(context, Boolean.class);

                        if (result) {
                                count++;
                        }
                }

                long end = System.currentTimeMillis();

                System.out.println("Adults: " + count);
                System.out.println("Time (good): " + (end - start) + " ms");
        }

        private static void bad(ExpressionParser parser, List<User> users, String expression) {
                long start = System.currentTimeMillis();

                int count = 0;

                StandardEvaluationContext context = new StandardEvaluationContext();

                for (User user : users) {
                        // ❌ parsing every iteration
                        Expression exp = parser.parseExpression(expression);

                        context.setRootObject(user);

                        Boolean result = exp.getValue(context, Boolean.class);

                        if (result) {
                                count++;
                        }
                }

                long end = System.currentTimeMillis();

                System.out.println("Adults: " + count);
                System.out.println("Time (bad): " + (end - start) + " ms");
        }

        private record User(int age) {
        }

        private static List<User> generateUsers(int size) {
                List<User> list = new ArrayList<>();
                Random random = new Random();
                for (int i = 0; i < size; i++) {
                        list.add(new User(random.nextInt(100)));
                }
                return list;
        }
}
/**
 * Output (will vary based on machine and JVM optimizations):
 * Adults: 810142
 * Time (bad): 1889 ms
 * 
 * Adults: 810142
 * Time (good): 414 ms
 */

3.4 Security Risks

SpEL expressions can execute arbitrary code if they are constructed from untrusted input, which can lead to security vulnerabilities such as remote code execution.

Dangerous Example :

parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('rm -rf /')");

Best Practices :

Never evaluate untrusted input and use SimpleEvaluationContext for restricted access.

package com.example.spel;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

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

public class SpELExample31 {
        public static void main(String[] args) throws IOException, InterruptedException {
                String input = "T(java.lang.Runtime).getRuntime().exec('date')";

                // unsafe: allows access to all classes and methods
                run(new StandardEvaluationContext(), input);

                System.out.println();

                // safe: only allows access to properties and methods of the root object
                run(SimpleEvaluationContext.forReadOnlyDataBinding().build(), input);
        }

        private static void run(EvaluationContext context, String input) throws IOException, InterruptedException {
                ExpressionParser parser = new SpelExpressionParser();

                Expression expression = parser.parseExpression(input);

                Process process = (Process) expression.getValue(context);

                displayExecutionOutput(process);

                process.waitFor();
        }

        private static void displayExecutionOutput(Process process) throws IOException {
                BufferedReader reader = new BufferedReader(
                                new InputStreamReader(process.getInputStream()));

                String line;
                while ((line = reader.readLine()) != null) {
                        System.out.println("OUTPUT: " + line);
                }

                BufferedReader errorReader = new BufferedReader(
                                new InputStreamReader(process.getErrorStream()));

                while ((line = errorReader.readLine()) != null) {
                        System.err.println("ERROR: " + line);
                }
        }
}
/**
 * Output:
 
        OUTPUT: Sat Apr  4 05:40:17 PM CEST 2026

        Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1005E: Type cannot be found 'java.lang.Runtime'
                at org.springframework.expression.spel.support.SimpleEvaluationContext.lambda$static$0(SimpleEvaluationContext.java:105)
                at org.springframework.expression.spel.ExpressionState.findType(ExpressionState.java:176)
                at org.springframework.expression.spel.ast.TypeReference.getValueInternal(TypeReference.java:71)
                at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:60)
                at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:96)
                at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
                at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:267)
                at com.example.spel.SpELExample31.run(SpELExample31.java:32)
                at com.example.spel.SpELExample31.main(SpELExample31.java:24)
 */

3.5 ${} is not Spring SpEL

${} is not Spring SpEL, but rather Spring’s property placeholder syntax.

It is used for externalized configuration and is Resolved by Spring’s property resolution system (e.g. Environment, PropertySources).

It typically pulls values from :

  • application.properties;
  • application.yml;
  • environment variables.

Example :

@Value("${server.port}")

private int port;

In contrast, SpEL expressions are evaluated by the SpEL engine and can contain complex logic, method calls, and access to beans in the Spring context.

Example :

@Value("#{2 + 3}")

private int result;

Spring allows combining them :

Example :

@Value("#{${my.number} * 2}")

private int doubled;