Spring Expression Language ("SpEL" for short) is a powerful expression language that supports querying and manipulating an object graph at runtime. The language's syntax is similar to Unified EL, but offers additional features, most notably method invocation and basic string templating functionality.
Although there are several other Java expression languages - OGNL, MVEL, JBoss EL and others - the Spring Expression Language was created to give the Spring community a single, well-supported expression language that can be used across all products in the Spring portfolio. Features of the language are determined by the requirements of projects in the Spring portfolio, including requirements for tools to support code completion in Spring Tools for Eclipse. However, SpEL is based on a technology-independent API that allows the integration of other expression language implementations should the need arise.
Although SpEL is the foundational expression evaluation product in the Spring portfolio, it is not directly related to Spring and can be used independently. To demonstrate self-containment, many of the examples in this chapter use SpEL as if it were an independent expression language. This requires creating several boot infrastructure classes, such as a parser. Most Spring users don't need to deal with this framework, so they can instead create just strings of expressions to evaluate. An example of such a typical use is the integration of SpEL in the creation of XML-based or annotation-based bean definitions.
This chapter discusses the features of the
expression language, its API, and the language syntax. In some places, the Inventor
and
Society
classes are used as targets for evaluating expressions. The declarations of these classes and
the data used to populate them are listed at the end of the chapter.
The expression language supports the following functionality:
Literal expressions (literals);
Boolean and relational operators;
Regular expressions;
Class -expressions;
Access to properties, arrays, lists and associative arrays;
Access to methods;
Relationship operators;
Assignment;
Calling constructors;
Links to bins;
Constructing an array;
Embedded lists;
Embedded associative arrays;
Ternary operator;
Variables;
User-defined functions;
Projection of collections;
Fetching collections ;
Template expressions.
Computation
This section introduces the simple use of the SpEL and their expression language.
The following code uses the SpEL API to evaluate the literal string expression Hello
World
.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
- The value of the message variable is
'Hello World'
.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'")
val message = exp.value as String
- The value of the message variable is
'Hello World'
.
The SpEL classes and interfaces you are most likely to use are in the org.springframework.expression
package and its subpackages, such as spel.support
.
The ExpressionParser
interface is responsible for parsing the expression string. In the
previous example, the expression string is a string literal, denoted by the single quotes surrounding
it. The Expression
interface is responsible for evaluating a previously defined expression
string. Two exceptions ParseException
and EvaluationException
can be thrown
when calling parser.parseExpression
and exp.getValue
, respectively.
SpEL supports a wide range of functionality, such as method invocation, property access, and constructor invocation.
In the following method invocation example, we call the concat
method on a string literal:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
- The value of
message
is now 'Hello World!'.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')")
val message = exp.value as String
- Value
message
is now 'Hello World!'.
The following example JavaBean property call calls the String
property Bytes
:
ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
- This line converts a literal to a byte array.
val parser = SpelExpressionParser()
// accesses 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes")
val bytes = exp.value as ByteArray
- This line converts a literal to a byte array.
SpEL also supports nested properties using standard dot notation (for example, prop1.prop2.prop3
),
as well as setting property values accordingly. It is also possible to access public fields.
The following example shows how to use dot notation to get the length of a literal:
ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
'Hello World'.bytes.length
specifies the length of the literal.
val parser = SpelExpressionParser()
// accesses 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length")
val length = exp.value as Int
'Hello World'.bytes.length
specifies the length of the literal.
The String constructor can be called instead of using a string literal, as shown in the following example:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
- Create a new
String
from a literal and convert it to uppercase.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")
val message = exp.getValue(String::class.java)
- Create a new
String
from a literal and convert it to uppercase.
Note the use of the generic method: public <T> T getValue(Class<T>
desiredResultType)
. Using this method eliminates the need to cast the expression value to the
desired result type. If the value cannot be cast to type T
or converted using a
registered type converter, then an EvaluationException
is thrown.
A more common use of SpEL is to provide an expression string that is evaluated against a specific
object instance (called the root object). The following example shows how to get the
name
property from an instance of the Inventor
class or create a Boolean
Boolean condition:
// Create and configure the calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
// Parse the name as an expression String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla" exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and configure the calendar
val c = GregorianCalendar() c .set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name")
// Parse the name as an expression
val name = exp.getValue( tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
Basic information about EvaluationContext
The EvaluationContext
interface is used in evaluation expressions to resolve properties,
methods, or fields, and to perform type conversions. Spring provides two implementations.
SimpleEvaluationContext
: Exposes a subset of the core SpEL language features and configuration options for categories of expressions that do not require full SpEL syntax and must be are significantly limited. Examples include, but are not limited to, data binding expressions and property-based filters.StandardEvaluationContext
: Opens the full range of SpEL language capabilities and configuration options. You can use it to specify the default root object and to configure all available evaluation-related strategies.
SimpleEvaluationContext
is designed to provide partial syntax support only SpEL language. It
excludes references to Java types, constructors, and bean references. It also requires an explicit
choice of the level of support for properties and methods in expressions. By default, the static factory
method create()
allows read-only access to properties. You can also get a constructor to
fine-tune the required level of support, focusing on one specific parameter or some combination of the
following parameters:
Only a special
PropertyAccessor
(no reflection)Data binding properties for read-only access
Data binding properties for read-write access
Type conversion
By default, SpEL uses the conversion service available in the Spring core (org.springframework.core.convert.ConversionService
).
This conversion service comes with many built-in converters to perform general conversions, but is also
fully extensible to allow custom conversions between types to be added. In addition, it can work with
generics. This means that if you work with typed types in expressions, SpEL attempts to perform
conversions to preserve type correctness for any objects it encounters.
What does this actually mean? Suppose an assignment with setValue()
is used to set the
List
property. The type of the property is actually List<Boolean>
. SpEL
recognizes that list items must be converted to Boolean
before being placed into it. The
following example shows how to do this:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed here as a String. SpEL and the conversion service
// recognizes that it should be Boolean and converts it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b - false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed as a String here. SpEL and the conversion service
// recognizes that it should be Boolean and converts it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b - false
val b = simple.booleanList[0]
Parser Configuration
You can configure the SpEL expression parser using the parser configuration object (org.springframework.expression.spel.SpelParserConfiguration
).
The configuration object controls the operating logic of some of the components of the expression. For
example, if you are indexing an array or collection and the element at the specified index is
null
, SpEL can automatically create that element. This makes sense when using expressions
that consist of a chain of property references. If you index an array or list and specify an index that
is beyond the current size of the array or list, SpEL can automatically grow the array or list to
accommodate that index. To add an element at the specified index, SpEL will attempt to create the
element using the default element type constructor before setting the specified value. If the element
type does not have a default constructor, null
will be added to the array or list. If there
is no built-in or special resolver that knows how to set the value, null
will remain in the
array or list at the specified index. The following example demonstrates automatic list expansion:
class Demo {
public List<String> list;
}
// Enable:
// - automatic initialization of an empty (null) link
// - the autocollection is expanded
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo(); Object o = expression.getValue(demo);
// demo.list will now be an actual collection of 4 entries
// Each entry is a new empty line
class Demo {
var list: List<String>? = null
}
// Enable:
// - automatic initialization of an empty (null) link
// - the autocollection is expanded
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3] ")
val demo = Demo() val o = expression.getValue(demo)
// demo.list will now be an actual collection of 4 entries
// Each entry is a new empty string
Compiling with SpEL
Spring Framework 4.1 includes a basic expression compiler. Typically, expressions are interpreted, which provides greater dynamic flexibility in evaluation, but does not provide optimal performance. For occasional use of expressions this is fine, but when using other components such as Spring Integration, performance may be a factor and there is no real need for dynamism.
The SpEL compiler is designed to meet this need. At evaluation time, the compiler generates a Java class that embodies the execution logic of the expression at runtime, and uses this class to perform much faster expression evaluation. Because there is no typing for expressions, the compiler compiles using the information collected during interpreted evaluations of the expression. For example, it does not recognize the type of a property reference purely from the expression, but during the first interpreted calculation it begins to understand what it is. Naturally, compiling from such derived information can lead to problems down the line if the types of the various elements of the expression change over time. For this reason, compilation is best for expressions whose type information will not change when evaluated repeatedly.
Consider the following basic expression:
someArray[0].someProperty.someOtherProperty < 0.1
Because the previous expression involves array access, some property dereferencing, and numeric operations, the performance benefit can be very noticeable. In the example microbenchmark test run of 50,000 iterations, it took 75 ms to evaluate using the interpreter, but only 3 ms using the compiled version of the expression.
Compiler configuration
The compiler is not enabled by default, but you can activate it in one of two different ways. You can enable it through the parser configuration procedure, or through a Spring property if the use of SpEL is embedded in another bean. This section discusses both of these options.
The compiler can operate in one of three modes, which are reflected in the enum type org.springframework.expression.spel.SpelCompilerMode
.
The modes are as follows:
OFF
(default): The compiler is turned off.IMMEDIATE
: In immediate mode, expressions are compiled as quickly as possible. Typically this occurs after the first interpreted calculation. If the compiled expression fails (usually due to a type change, as described earlier), the caller to the expression evaluation code receives an exception.MIXED
: In In mixed mode, expressions switch seamlessly between interpreted and compiled mode over time. After a certain number of interpreted executions, they are switched to the compiled form, but if something goes wrong in the compiled form (for example, the type changes, as described earlier), the expression is automatically switched back to the interpreted form. Some time later, the expression may generate another compiled form and switch to that. Basically, an exception that the user receives inIMMEDIATE
mode is instead handled internally.
The IMMEDIATE
mode exists because that the MIXED
mode may cause problems with
expressions that have side effects. If a compiled expression produces a catastrophic error after being
partially successful, it may have already performed some action that affected the state of the system.
If this happens, the calling code may not want it to be silently re-executed in interpreted mode, since
part of the expression might be executed twice.
After choosing a mode, use SpelParserConfiguration
to configure the parser. The following
example shows how to do this:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
If you specify a compiler mode, you can also specify a class loader (passing null is allowed). Compiled expressions are defined in a child classloader created under any specified class. It is important to ensure that if a class loader is specified, it can recognize all types involved in the expression evaluation process. If you do not specify a class loader, the default class loader will be used (usually the context class loader for the thread that is executed while expressions are evaluated).
The second method of configuring the compiler is intended to be used if SpEL is implemented in any -
another component, and it is not possible to configure it through a configuration object. In these
cases, you can set the spring.expression.compiler.mode
property via a system property from
the JVM (or via the SpringProperties
mechanism) on one of the enum type values SpelCompilerMode(off
,
immediate
or mixed
).
Compiler limitations
Starting with Spring Framework 4.1, a basic compilation framework is used. However, the framework does not yet support the compilation of all types of expressions. The initial focus was on common expressions that are likely to be used in performance-critical contexts. The following types of expressions cannot currently be compiled:
Assignment-related expressions;
Expressions that use the conversion service;
Expressions that use special recognizers or accessors;
Expressions that use selection or projection.
More types of expressions will be able to be compiled in the future.
Expressions in bean definitions
SpEL expressions can be used to define instances of BeanDefinition
with XML-based or
annotation-based configuration metadata. In both cases, the syntax for defining an expression is #{
<expression string> }
.
4.2.1. XML Configuration
The value of a property or constructor argument can be specified using expressions, as shown in the following example:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
All beans in the application context are available as predefined variables with their normal bean name.
This includes standard context beans such as environment
(like org.springframework.core.env.Environment
),
as well as systemProperties
and systemEnvironment
(type Map<String,
Object>
) to gain access to the runtime environment.
The following example demonstrates access to the systemProperties
bean as for the SpEL
variable:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
Note that in this case you do not need to prefix the predefined variable with the symbol
#
.
You can also reference other bean properties by name, as shown in the following example:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
Annotation configuration
To set a default value, you can place the annotation @Value
to fields, methods, method
parameters, or constructor parameters.
In the following example, a default value is set for a field:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
The following example shows the equivalent, but for a property setter:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
Automatically discovered and associated methods and constructors can also use the @Value
annotation , as shown in the following examples:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
private lateinit var defaultLocale: String
@Autowired
fun configure(movieFinder: MovieFinder,
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
this.movieFinder = movieFinder
this.defaultLocale = defaultLocale
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
// ...
}
GO TO FULL VERSION