Literal expressions

The following types of literal expressions are supported: strings, numeric values (int, real, hex), boolean and null. Strings are separated by single quotes. To enclose the quote itself in a string, use two single quote characters.

The following listing shows a simple use of literals. They are typically not used in isolation, as in this case, but as part of a more complex expression - for example, when using a literal on one side of a logical comparison operator.

Java

ExpressionParser parser = new SpelExpressionParser();
// has the value "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// turns out to be 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
Kotlin

val parser = SpelExpressionParser()
// has the value "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// turns out 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value

Numbers support the use of negative sign, scientific notation, and decimal points. By default, real numbers are parsed using Double.parseDouble().

Properties, arrays, lists, associative arrays, and indexers

Navigate using links to properties are simple. To do this, use a period to indicate the value of the attached property. The Inventor class instances, pupin and tesla, have been populated with data. To move "down" through the object graph and get Tesla's birth year and Pupin's birth city, we use the following expressions:

Java

// turns out to be 1856
int year = ( Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
Kotlin
 
// turns out to be 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String

Case insensitiveness is allowed for the first letter of property names. So the expressions in the above example can be written as Birthdate.Year + 1900 and PlaceOfBirth.City, respectively. In addition, properties can be accessed through method calls - for example, getPlaceOfBirth().getCity() instead of placeOfBirth.city.

The contents of arrays and lists can be obtained using square bracket notation, as shown in the following example:

Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Array of inventions
// has the value "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);
// Member list
// has the value "Nikola Tesla".
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);
// List of members
// has the value "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);
Kotlin

val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// Array of inventions
// matters "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String::class.java)
// Member list
// has the value "Nikola Tesla".
val name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String::class.java)
// List of members
// has the value "Wireless communication"
val invention = parser.parseExpression(" members[0].inventions[6]").getValue(
        context, ieee, String::class.java)

The contents of associative arrays can be obtained by specifying the literal value of the key in parentheses. In the following example, since the keys for the associative array officers are strings, we can specify string literals:

Java

// Officer dictionary
Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);
// has the value "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");
Kotlin

// Officer dictionary
val pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor::class.java)
// has value "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String::class.java)
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia")

Embeddable lists

You can directly express lists in an expression using the {} notation.

Java

// defined as a Java list containing four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
Kotlin

// defined as a Java list containing four numbers
val numbers = parser.parseExpression("{1,2,3,4}" ).getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>

{} itself means an empty list. For performance reasons, if the list consists entirely of fixed literals, a list of constants is created to represent the expression (rather than constructing a new list each time it is evaluated).

Inline Maps

You can also express Map directly in an expression using the {key:value} notation. The following example shows how to do this:

Java

// is defined as a Java Map containing two entries
Map inventorInfo = (Map) parser.parseExpression("{ name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue (context);
Kotlin

// is defined as a Java Map containing two entries
val inventorInfo = parser. parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *>

{:} itself means an empty Map. For performance reasons, if the Map itself consists of fixed literals or other nested constant structures (lists or Maps), a Map of constants is created to represent the expression (rather than constructing a new Map each time it is evaluated). It is not necessary to enclose Map keys in quotes (unless the key contains a period (.)). The examples above do not use quotes.

Constructing Arrays

You can create arrays using familiar Java syntax, optionally specifying an initializer so that the array is populated during construction. The following example shows how to do this:

Java

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multidimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
Kotlin

val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multidimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue( context) as Array<IntArray>

Currently, you cannot specify an initializer when creating a multidimensional array.

Methods

You can call methods using typical Java programming syntax. You can also call methods on literals. Variable arguments are also supported. The following examples show how to call methods:

Java

// String literal having the value "bc"
String bc = parser.parseExpression("'abc'. substring(1, 3)").getValue(String.class);
// turns out to be true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Kotlin

// string literal having the value "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class. java)
// turns out to be true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean::class.java)

Operators

The Spring Expression Language supports the following types of operators:

  • Relational operators

  • Logical operators

  • Mathematical operators

  • Assignment operator

Relational operators

Relational operators (equal, not equal, less than, less than or equal, greater than, and greater than greater than or equal) are supported using standard operator notation. The following listing provides several example operators:

Java

// turns out to be true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean. class);
// turns out to be false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// turns out to be true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
Kotlin

// turns out to be true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// turns out to be false
val falseValue = parser.parseExpression( "2 < -5.0").getValue(Boolean::class.java)
// turns out to be true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java) 

Comparisons of "greater than" and "less than" with null obey a simple rule: null is considered nothing (it is NOT zero). As a consequence, any other value is always greater than null(X > null is always true) and no other value is ever less than nothing (X < null code> is always false).

If you prefer numeric comparisons, avoid null number-based comparisons in favor of null-based comparisons (e.g. X > 0 or X < 0).

In addition to the standard relational operators, SpEL supports the instanceof operator and a matches operator based on regular expressions. The following list provides examples of both options:

Java

// turns out to be false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);
// turns out to be true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// turns out to be false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
Kotlin

// turns out to be false
val falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// turns out to be true
val trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// turns out to be false
val falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue (Boolean::class.java)
Be careful with primitive types because they are packaged in their own wrapper types. For example, 1 instanceof T(int) turns out to be false, and 1 instanceof T(Integer) turns out to be true, like as expected.

Each character operator can also be specified as a purely literal equivalent. This avoids problems when the characters used have a special meaning for the type of document in which the expression is embedded (for example, an XML document). The text equivalents are:

  • lt (<)

  • gt (>)

  • le (<=)

  • ge (>=)

  • eq (==)

  • ne (!=)

  • div (/)

  • mod (%)

  • not (!).

All text operators are case insensitive.

Logical operators

SpEL supports the following logical operators:

  • and (&&)

  • or (||)

  • not (!)

The following example shows how to use logical operators:

Java

// -- AND --
// turns out to be false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// turns out to be true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// turns out to be true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// turns out to be true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// turns out to be false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Kotlin

// -- AND --
// turns out to be false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// turns out to be true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// turns out to be true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// turns out to be true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// turns out to be false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

Math Operators

The addition operator (+) can be used for both numbers and strings. The subtraction (-), multiplication (*), and division (/) operators can only be used for numbers. You can also use the remainder (%) and exponential growth (^) operators for numbers. Standard operator precedence applies. The following example shows the mathematical operators used:

Java

// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);// 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Taking the remainder
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
Kotlin

// Addition
val two = parser.parseExpression("1 + 1" ).getValue(Int::class.java) // 2
val testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class .java) // -9000
// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
// Division val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
// Taking the remainder
val three = parser.parseExpression("7 % 4").getValue(Int::class. java) // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3 *8").getValue(Int::class.java) // -21

Assignment operator

To set a property, use the assignment operator (=). This is usually done inside a setValue call, but can also be done inside a getValue call. The following listing shows both ways to use the assignment operator:

Java

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// alternative to
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic' ").getValue(context, inventor, String.class);
Kotlin

val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")
// alternative
val aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)

Types

You can use the special operator T to specify an instance of java.lang.Class (type). Static methods are also called using this operator. StandardEvaluationContext uses TypeLocator to look up types, and StandardTypeLocator (which can be replaced) is built to take advantage of the java.lang package. This means that T() references to types from the java.lang package do not need to be fully qualified, but all other type references must be qualified. The following example shows how to use the T operator:

Java

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);
Kotlin

val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)
val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)
val trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode ).FLOOR")
        .getValue(Boolean::class.java)

Constructors

You can call constructors using the new operator. You need to use the fully qualified class name for all types except those in the java.lang(Integer, Float, String package and so on Further). The following example shows how to use the new operator to call constructors:

Java

Inventor einstein = p.parseExpression(
        "new org. spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);
// create a new Inventor instance in the add() method List
p.parseExpression(
         "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext );
Kotlin

val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)
// create a new Inventor instance in the add() method List
p.parseExpression(
        "Members.add(new org.spring .samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

Variables

You can refer to variables in an expression using the syntax #variableName. Variables are set using the setVariable method in the EvaluationContext implementation.

Valid variable names must consist of one or more of the following supported characters.

  • letters: A to Z and a to z

  • numbers: from 0 to 9

  • underscore: _

  • dollar sign: $

The following example shows how to use variables.

Java

Inventor tesla = new Inventor( "Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
Kotlin

val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"

Variables #this and #root

The variable #this is always defined and refers to the current calculation object (relative to which unqualified references are resolved). The #root variable is always defined and refers to the root context object. Although #this can change as the expression's components are evaluated, #root always refers to the root. The following examples show how to use the #this and #root variables:

Java

// create array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create a parser and set the "primes" variable as an array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...})
// turns out to be [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);
Kotlin

// create an array of integers
val primes = ArrayList<Int>() primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// create a parser and set the variable "primes" as an array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// all primes > 10 from the list (using selection ?{...})
// turns out to be [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
        "#primes.?[#this>10]").getValue(context) as List< ;Int>

Functions

You can extend SpEL by registering user-defined functions that can be called on the expression line. The function is registered via EvaluationContext. The following example shows how to register a user-defined function:

Java

Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
Kotlin

val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)

For example, consider the following utility method that inverts line:

Java

public abstract class StringUtils {
    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
Kotlin

fun reverseString(input: String): String {
    val backwards = StringBuilder(input.length)
    for (i in 0 until input.length) {
        backwards.append(input[input.length - 1 - i])
    }
    return backwards.toString()
}

You can then register and use the previous method, as shown in the following example:

Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
Kotlin

val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String::class.java)
            

Bean references

If the evaluation context has been configured with a bean resolver, you will be able to search for beans in an expression using the @ symbol. The following example shows how to do this:

Java

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will cause resolve(context, "something") to be called on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
Kotlin

val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will call resolve(context, "something ") for MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)

To access the bean itself - factory, you should replace the bean name with the symbol &. The following example shows how to do this:

Java

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will cause resolve(context,"&foo") to be called on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
Kotlin

val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will call resolve(context ,"&foo") for MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)

Ternary (If-Then-Else) operator

You can use the ternary operator to perform if-then-else conditional jump logic within an expression. The following listing shows a simple example:

Java

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Kotlin

val falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String::class.java)

In this case, the boolean false results in returning the string value 'falseExp'. The following is an example closer to practice:

Java

parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
Kotlin

parser.parseExpression("name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

See. See the following section on the Elvis operator for even shorter ternary operator syntax.

Elvis operator

The Elvis operator is a shorthand for ternary operator syntax and is used in the Groovy language. When using ternary operator syntax, you typically have to repeat the variable twice, as shown in the following example:


String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

You can use the Elvis operator instead (so named because of the similarity with Elvis Presley's hair). The following example shows how to use the Elvis operator:

Java

ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'
Kotlin

val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name)  // 'Unknown'

The following listing shows a more complex example:

Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
Kotlin

val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Elvis Presley

You can use the Elvis operator to apply default values in expressions. The following example shows how to use the Elvis operator in an expression annotated with @Value:

@Value("#{systemProperties['pop3.port'] ?: 25}")

This injects the system property pop3.port if defined, or 25 if not.

Safe navigation operator

The Safe Navigation Operator is used to prevent NullPointerException and comes from the language Groovy. Typically, if you have a reference to an object, you may want to ensure that it is not empty before accessing the object's methods or properties. To avoid this, the safe navigation operator returns null rather than throwing an exception. The following example shows how to use the safe navigation operator:

Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
Kotlin

val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // Smiljan
tesla.setPlaceOfBirth(null)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city) // null - does not throw NullPointerException!!!

Selection of collections

Selection is A powerful expression language feature that allows you to transform a source collection into another collection by selecting from its records.

Selecting uses the syntax .?[selectionExpression]. It filters the collection and returns a new collection containing a subset of the original elements. For example, using sampling, you can easily obtain a list of Serbian inventors, as shown in the following example:

Java

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext);
Kotlin

val list = parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>

Sampling is supported for arrays and anything that implements java.lang.Iterable or java.util.Map. For a list or array, the selection criteria are evaluated on each individual element. With respect to an associative array, the selection criteria are evaluated on each element of the associative array (Java objects of type Map.Entry). Each associative array entry has a key and a value, available as properties for use when fetching.

The following expression returns a new associative array consisting of those elements of the original associative array in which the element value is less than 27:

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

In addition to returning all selected elements, you can only return the first or last element. To get the first element that matches the selection criteria, the syntax is .^[selectionExpression]. To get the last element that matches the selection criteria, the syntax is .$[selectionExpression].

Projection of collections

Projection allows a collection to control the evaluation of a subexpression, and the result is a new collection. The syntax for projection is .![projectionExpression]. For example, let's say we have a list of inventors, but we want a list of the cities in which they were born. Essentially, we want to compute 'placeOfBirth.city' for each entry in the list of inventors. The following example uses a projection to do this:

Java
// returns ['Smiljan', 'Idvor' ].
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
Kotlin
// returns ['Smiljan', 'Idvor' ].
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>

Projection is supported for arrays and anything that implements java.lang.Iterable or java.util.Map. When you use an associative array to control a projection, the projection expression is evaluated over each entry in that associative array (represented as Map.Entry in Java). The result of a projection over an associative array is a list consisting of evaluating the projection expression for each entry in the associative array.

Expression templating

Expression templates allow you to mix literal text with one or more blocks of calculations. Each block of calculations is delimited by prefix and suffix characters that you can define. Typically, #{ } are used as delimiters, as shown in the following example:

Java
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);
// it turns out "the random number is 0.7038186818312008"
Kotlin
val randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        TemplateParserContext()).getValue(String::class.java)
// it turns out "the random number is 0.7038186818312008"

The string is calculated by concatenating the literal text 'random number is ' with the result of calculating the expression inside the delimiter #{ } (in this case, the result of calling the random( )). The second argument of the parseExpression() method is of type ParserContext. The ParserContext interface is used to influence how an expression is parsed to provide expression templating functionality. The following is the definition of TemplateParserContext:

Java
public class TemplateParserContext implements ParserContext {
    public String getExpressionPrefix() {
        return "#{";
    }
    public String getExpressionSuffix() {
        return "}";
    }
    public boolean isTemplate() {
        return true;
    }
}
Kotlin
class TemplateParserContext : ParserContext {
    override fun getExpressionPrefix(): String {
        return "#{"
    }
    override fun getExpressionSuffix(): String {
        return "}"
    }
    override fun isTemplate(): Boolean {
        return true
    }
}