Home | Send Feedback | Share on Bluesky |

A look at Spring Expression Language (SpEL)

Published: 6. March 2026  •  java, spring

Spring Expression Language (SpEL) is an expression language from the Spring ecosystem. You might have seen it in @Value annotations in a Spring application to access properties or system variables. Expressions in SpEL are expressed as strings, and they can be evaluated at runtime against a context that provides variables, functions, and a root object.

The nice thing about the SpEL library is that you can use it outside the Spring ecosystem as well. It’s a general-purpose expression language that can be embedded in any Java application.

All you need is to add the spring-expression dependency.

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>...</version>
  </dependency>

Basic SpEL features

To use SpEL, you create an ExpressionParser (usually SpelExpressionParser), and then call parseExpression with your expression string. You can then call getValue to evaluate it and retrieve the result.

Literals

This snippet evaluates a simple arithmetic expression. The getValue method takes a target type to convert the result to.

        ExpressionParser parser = new SpelExpressionParser();

        Integer result = parser.parseExpression("20 + 22").getValue(Integer.class);
        assert result == 42 : "Expected 42 but got " + result;  

SpelExamples.java


Properties and methods

Often you want to work with some data. For this purpose you can pass an EvaluationContext that provides variables and a root object. This example creates an instance of a Customer record with some sample data, and then evaluates the expression name to access the name property of the customer. The customer is the root object in the evaluation context, so you can access its properties directly by name. The result is then converted to a String.

    public record Customer(String name, int age, String preferredContact, List<String> tags,
            Map<String, String> attributes) {
    }

SpelExamples.java

        Customer customer = new Customer("Ada", 34, null, List.of("vip", "beta"),
            Map.of("country", "CH", "currency", "CHF"));        
        var ctx = new StandardEvaluationContext();
        String name = parser.parseExpression("name").getValue(ctx, customer, String.class);
        assert "Ada".equals(name) : "Expected 'Ada' but got '" + name + "'";

SpelExamples.java

Instead of passing the root object directly to getValue, you can also set it when creating the context. This example also shows you that you can call methods. The name property is a String, so you can call toUpperCase() on it.

        StandardEvaluationContext context = new StandardEvaluationContext(customer);
        String upperName = parser.parseExpression("name.toUpperCase()").getValue(context, String.class);
        assert "ADA".equals(upperName) : "Expected 'ADA' but got '" + upperName + "'";

SpelExamples.java


Boolean logic and conditional expressions

These examples show how to use boolean logic and conditional expressions in SpEL. They all use the same context as before, which has the customer as the root object. This allows the expressions to access properties with their names (age and preferredContact).

SpEL not only supports the logical operators you know from Java (&&, ||, !), but also human-readable versions: and, or, not.

        context.setVariable("orderAmount", 420.75);
        Boolean booleanExpression = parser.parseExpression("age >= 18 and #orderAmount > 100").getValue(context, Boolean.class);
        assert booleanExpression : "Expected true but got " + booleanExpression;        

SpelExamples.java

With the setVariable method you can add variables to the context, which are accessed with # in the expression (#orderAmount and #riskScore).

        context.setVariable("riskScore", 71);      
        String ternary = parser.parseExpression("#riskScore > 70 ? 'REVIEW' : 'AUTO'").getValue(context, String.class);
        assert "REVIEW".equals(ternary) : "Expected 'REVIEW' but got '" + ternary + "'";

SpelExamples.java

Two features you don't find in Java that SpEL supports are the Elvis operator ?: and the Safe Navigation operator ?..

The Elvis operator is a shorthand for a ternary expression that checks for null. For example, a ?: b is equivalent to a != null ? a : b. Note that the SpEL Elvis operator also treats an empty String as a null object.

The Safe Navigation operator allows you to call methods or access properties on an object that might be null without throwing a NullPointerException. For example, a?.b will return null if a is null, instead of trying to access b and throwing an exception.

In this example, SpEL calls the method toUpperCase() only if preferredContact is not null. If it is null, the expression evaluates to null, and then the Elvis operator provides a default value of 'EMAIL'.

        String safeNavigation = parser.parseExpression("preferredContact?.toUpperCase() ?: 'EMAIL'").getValue(context, String.class);
        assert "EMAIL".equals(safeNavigation) : "Expected 'EMAIL' but got '" + safeNavigation + "'";

SpelExamples.java


Collection access

To access an array or list element, you can use the index operator []. Even though tags is an instance of List, SpEL allows access to the elements with the same syntax as an array. The expression tags[0] retrieves the first element of the list. [] can also be used to access characters in a string.

        String listIndex = parser.parseExpression("tags[0]").getValue(context, String.class);
        assert "vip".equals(listIndex) : "Expected 'vip' but got '" + listIndex + "'";

SpelExamples.java

Accessing a map value is similar, but you need to use the key as the index. In this example, attributes is a Map<String, String>, so you can access the value associated with the key 'country' using attributes['country'].

        String mapAccess = parser.parseExpression("attributes['country']").getValue(context, String.class);
        assert "CH".equals(mapAccess) : "Expected 'CH' but got '" + mapAccess + "'";

SpelExamples.java


Another thing you can do is filter a collection with the selection operator .?[selectionExpression]. This operator evaluates the selectionExpression for each element in the collection, and returns a list of those elements for which the expression is true. In this example, the expression tags.?[#this.startsWith('v')] filters the tags list to include only those elements that start with 'v'. This works for any object that implements java.lang.Iterable or is an instance of java.util.Map.

        List<String> selection = parser.parseExpression("tags.?[#this.startsWith('v')]").getValue(context, List.class);
        assert "vip".equals(selection.getFirst()) : "Expected 'vip' but got '" + selection + "'";

SpelExamples.java

In addition to returning all the selected elements, you can retrieve only the first or the last element. To obtain the first element matching the selection expression, the syntax is .^[selectionExpression]. To obtain the last element matching the selection expression, the syntax is .$[selectionExpression].


Another SpEL operator for collections is the projection operator .![projectionExpression]. This operator evaluates the projectionExpression for each element in the collection, and returns a list of the results. In this example, the expression tags.![#this.toUpperCase()] transforms each element in the tags list to its uppercase version. Like the selection operator, the projection operator also works for any object that implements java.lang.Iterable or is an instance of java.util.Map.

        List<String> projection = parser.parseExpression("tags.![#this.toUpperCase()]").getValue(context, List.class);
        assert "VIP".equals(projection.getFirst()) && "BETA".equals(projection.getLast()) : "Expected 'VIP,BETA' but got '" + projection + "'";

SpelExamples.java

When the target of a projection operator is a Map, the result is a list of evaluations of the projection expression against each map entry, where the map entry is represented as a Map.Entry object. The Map.Entry object has two properties: key and value, which can be accessed in the projection expression. The following two examples show how to extract the keys and values of attributes.

        List<String> projectedKeys = parser.parseExpression("attributes.![key]").getValue(context, List.class);
        assert projectedKeys.size() == 2 && projectedKeys.containsAll(List.of("country", "currency")) : "Expected 'country,currency' but got '" + projectedKeys + "'";

        List<String> projectedValues = parser.parseExpression("attributes.![value]").getValue(context, List.class);
        assert projectedValues.size() == 2 && projectedValues.containsAll(List.of("CH", "CHF")) : "Expected 'CH,CHF' but got '" + projectedValues + "'";

SpelExamples.java


Static methods

The T operator is used to access a type and can then be used to call static methods on that type. This example calls the static method max from the java.lang.Math class.

        Integer typeReference = parser.parseExpression("T(java.lang.Math).max(10, age)").getValue(context, Integer.class);
        assert typeReference == 34 : "Expected 34 but got " + typeReference;

SpelExamples.java


Functions

With registerFunction, any method can be registered as a function in the evaluation context under a specific name. The expression can then call the function with #functionName.

        Method slugMethod = SpelExamples.class.getDeclaredMethod("slugify", String.class);
        context.registerFunction("slug", slugMethod);                
        String slug = parser.parseExpression("#slug(name)").getValue(context, String.class);
        assert "ada".equals(slug) : "Expected 'ada' but got '" + slug + "'";

SpelExamples.java

    public static String slugify(String value) {
        return value == null ? "" : value.trim().toLowerCase().replace(" ", "-");
    }

SpelExamples.java


Template expressions

Expression templates allow you to mix literal text with one or more evaluation blocks. Each evaluation block is delimited with prefix and suffix characters that you can define. When you use the TemplateParserContext, the default delimiters are #{ and }. The expression is evaluated by replacing each evaluation block with its result, while keeping the literal text unchanged.

        String message = parser
                .parseExpression("Customer #{name} in #{attributes['country']} -> #{#slug(name)}",
                        new TemplateParserContext())
                .getValue(context, String.class);
        assert "Customer Ada in CH -> ada".equals(message) : "Expected 'Customer Ada in CH -> ada' but got '" + message + "'";

SpelExamples.java

You can change the delimiters by passing them to the constructor of TemplateParserContext. For example, if you want to use ${ and } as delimiters, you can create a TemplateParserContext like this:

  TemplateParserContext customParserContext = new TemplateParserContext("${", "}");

Writing values

So far we only used SpEL to read values from the context, but you can also write values back to the context. The Expression interface has a method setValue that allows you to set a value for a given expression.

In this example, we create a mutable object that has setters for its properties. This object is used as the root object in the evaluation context. The expression preferredContact is a simple property access expression and calling setValue with this expression will set the value in the root object instance.

        MutableCustomer mutableCustomer = new MutableCustomer(null);
        StandardEvaluationContext mutableContext = new StandardEvaluationContext(mutableCustomer);
        parser.parseExpression("preferredContact").setValue(mutableContext, "SMS");
        assert "SMS".equals(mutableCustomer.getPreferredContact())
            : "Expected 'SMS' but got '" + mutableCustomer.getPreferredContact() + "'";

SpelExamples.java

SimpleEvaluationContext

All the examples above used StandardEvaluationContext. This context supports all features of SpEL. But sometimes you might want to restrict the features that are available in the evaluation context for security or simplicity reasons. For example, if you are evaluating expressions that come from an untrusted source, you might want to prevent them from calling methods or accessing certain properties.

For this reason, Spring SpEL also provides SimpleEvaluationContext, which is a more restricted context implementation. It focuses on a subset of essential SpEL features and customization options, targeting simple condition evaluation and in particular data binding scenarios. Features that are not supported in SimpleEvaluationContext include references to Java types, constructors, and bean references.

You can create a SimpleEvaluationContext with different levels of support for data binding. For example, you can create a read-only context with SimpleEvaluationContext.forReadOnlyDataBinding(), or a read-write context with SimpleEvaluationContext.forReadWriteDataBinding().

This example shows how to use a SimpleEvaluationContext with read-write data binding.

        ExpressionParser parser = new SpelExpressionParser();
        SimpleEvaluationContext readWriteContext = SimpleEvaluationContext.forReadWriteDataBinding().build();

        Person readWritePerson = new Person("Ada");
        String currentName = parser.parseExpression("name").getValue(readWriteContext, readWritePerson, String.class);
        assert "Ada".equals(currentName) : "Expected 'Ada' but got '" + currentName + "'";

        parser.parseExpression("name").setValue(readWriteContext, readWritePerson, "Grace");
        assert "Grace".equals(readWritePerson.getName())
                : "Expected 'Grace' but got '" + readWritePerson.getName() + "'";

SimpleEvaluationContextExamples.java

If you try to set a value in a read-only context, it will throw an exception.

        SimpleEvaluationContext readOnlyContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Person readOnlyPerson = new Person("Marie");
        String readOnlyName = parser.parseExpression("name").getValue(readOnlyContext, readOnlyPerson, String.class);
        assert "Marie".equals(readOnlyName) : "Expected 'Marie' but got '" + readOnlyName + "'";

        try {
            parser.parseExpression("name").setValue(readOnlyContext, readOnlyPerson, "Rosalind");
        }
        catch (Exception ex) {
            System.out.println(ex.getMessage());
            // EL1010E: Property or field 'name' cannot be set on object of type 'ch.rasc.speldemo.SimpleEvaluationContextExamples$Person' 
            // - maybe not public or not writable?
        }

SimpleEvaluationContextExamples.java

Compilation

Expressions are usually interpreted, which is not a problem when you only evaluate them once. For example, in Spring the @Value annotation is evaluated only once when the bean is created, so performance is not a concern. But if you use SpEL in a performance-critical context, such as in a loop or in a component that evaluates expressions frequently, the interpreted mode might not be fast enough. In such cases, you can use the SpEL compiler to improve performance.

To enable the SpEL compiler, you can configure the SpelParserConfiguration with a specific SpelCompilerMode. The following example shows how to do this when creating the parser. The mode variable can be set to one of the values of the SpelCompilerMode enum (OFF, IMMEDIATE, MIXED) depending on your needs.

        SpelParserConfiguration config = new SpelParserConfiguration(mode,
                SpelCompilationPerformanceExample.class.getClassLoader());
        SpelExpressionParser parser = new SpelExpressionParser(config);
        Expression expression = parser.parseExpression(EXPRESSION);
        SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(input).build();

SpelCompilationPerformanceExample.java

When the mode is set to IMMEDIATE, the expression will be compiled as soon as possible, typically after the first interpreted evaluation.

When the mode is set to MIXED, the system will silently switch between interpreted and compiled mode over time. After some number of successful interpreted runs, the expression gets compiled.

OFF is the default mode, which means that all expressions will be evaluated in interpreted mode.

SpEL can't compile every kind of expression. For example, expressions that involve assignment, rely on the conversion service, use custom resolvers, use overloaded operators, use Optional with the null-safe or Elvis operator, use array construction syntax, use selection or projection, or use bean references cannot be compiled. The primary focus of the SpEL compiler is on common expressions that are likely to be used in performance-critical contexts.

Check out the documentation for an up-to-date list of compiler limitations.

In this example, the difference between OFF and IMMEDIATE is significant, with a speedup of around 40x on my machine.

Use case: Rule Engine

In this section we will explore a possible use case for SpEL: implementing a simple rule engine for fee calculation. In this application we have a set of rules to calculate fees for payment transactions. Instead of hardcoding these rules in code, we want to externalize them and store them in a text file or a database. This way, we can change the rules without modifying the code, and we can even allow non-developers to edit the rules if needed.

In this example, the rules are stored in a text file with the following format:

# name|condition|feeExpression|description
BASE_CARD|paymentMethod == 'CARD'|amount * 0.0115 + 0.18|Card processing base fee
BASE_ACH|paymentMethod == 'ACH'|amount * 0.0025 + 0.05|ACH processing base fee

fee-rules.txt

The application is a Spring Boot application and contains a SpelFeeEngine service that loads the rules from the file, parses the condition and fee expressions, and stores them in a List (rules).

  private final List<Rule> rules;

  public SpelFeeEngine(ExternalFeeRuleLoader ruleLoader) {
    ExpressionParser parser = new SpelExpressionParser();
    this.rules = ruleLoader.load().stream()
        .map(rule -> new Rule(rule,
            parser.parseExpression(rule.conditionExpression()),
            parser.parseExpression(rule.feeExpression())))
        .toList();
  }

SpelFeeEngine.java


When the application wants to calculate the fee for a transaction, it creates a FeeRequest object that contains all the relevant information about the transaction. This object is used as the root object in the evaluation context when evaluating the rules.

public record FeeRequest(double amount, String currency, String paymentMethod,
    String merchantTier, String channel, boolean crossBorder, int installments,
    double monthlyVolume, int riskScore, double chargebackRate) {

  public double riskMultiplier() {
    return FeeMath.riskMultiplier(this.chargebackRate);
  }

  public int riskScoreDistanceFrom50() {
    return Math.abs(this.riskScore - 50);
  }
}

FeeRequest.java

The application calls the calculate method of the SpelFeeEngine with the FeeRequest. The method first creates a simple evaluation context that is read-only. Because we read expressions from an external source, we want to make sure these expressions can't modify anything.

    SimpleEvaluationContext context = SimpleEvaluationContext
        .forReadOnlyDataBinding().build();

SpelFeeEngine.java

The method then evaluates each rule's condition expression against the request.

    for (Rule rule : this.rules) {
      Boolean matches = rule.condition().getValue(context, request, Boolean.class);
      if (!Boolean.TRUE.equals(matches)) {
        continue;
      }

SpelFeeEngine.java

If a condition is true, it evaluates the fee expression.

      Double feeDelta = rule.fee().getValue(context, request, Double.class);

SpelFeeEngine.java

Wrapping up

Spring SpEL is a useful expression language that can be used in any Java application, not just in Spring. It provides a rich set of features for evaluating expressions against a context, including support for literals, properties, methods, boolean logic, collections, static methods, functions, and templates. SpEL also has a compiler that can improve performance in certain scenarios. You have seen that SpEL is not only useful in @Value annotations, but can also be used to implement dynamic expression evaluation in a programmatic way, such as in a rule engine for fee calculation.