Skip to main content

SpEL Syntax

The tSM Spring Expression Language (SpEL) couples a clean, readable syntax with the full power of Java, giving you an expressive way to automate anything inside the tSM platform.

This page is your one-stop reference: a quick tour of every major SpEL concept and a thorough specification of every syntax rule, console shortcut and best-practice pattern.


1 Running scripts

1.1 SpEL Console Usage & Shortcuts

1.1.1 Opening the Console

ModeHow to openWhen to use
Without contextIn the main left menu search for spel and select SpEL Console.Quick experiments, standalone utilities.
With contextOpen an entity (order, ticket, …), click the menu in the top-right corner and choose SpEL Console.When you need context variables such as #order or #ticket.

1.1.2 Autocomplete Cheat-sheet

ShortcutAction
Ctrl + SpaceOpen autocomplete suggestions.
Double Ctrl + SpaceShow a description of the highlighted suggestion.
Ctrl + EnterEvaluate the entire script.
Select text + Ctrl + EnterEvaluate only the selected expression.
Select documentation + EnterInsert example snippet of selected parameter.
@ then Ctrl + SpaceList all tSM services.
# then Ctrl + SpaceList standalone functions, variables and flow operators.
Enter inside ()Insert example arguments for a method.

1.1.3 Running Scripts & Expressions

  • Ctrl + Enter – evaluate the whole console buffer.
  • Select + Ctrl + Enter – evaluate only the highlighted expression (one at a time).

1.2 Example Script Structure — Multiple Expressions

// Example script – write as a whitespace-separated list of expressions
#orderTotal        = 250
#discountedTotal   = #orderTotal - 50
#date              = #now().formatted('dd-MM-yyyy HH:mm')
#user              = @user.user.get(#currentUser()).name
#orderSummary      = 'Price: ' + #discountedTotal + '; Date: ' + #date + '; User: ' + #user

Hint Most snippets in this guide can be pasted straight into the SpEL console. Use the little icon that appears in the top-right corner of each block.

Note A script is simply one or more expressions separated by whitespaces. Result of entire script is the result of the last command.

Rule of thumb: Any syntactically valid expression that returns exactly one value—be it a number, a string, a map or a whole object graph—counts as one expression.


2 SpEL Syntax Reference

2.1 Literals and Their Evaluation

// Literals
'Hello World'     // String
1234              // Decimal number
123.4             // Floating-point number
0x7FFFFFFF        // Hexadecimal
6.0221415E+23     // Scientific notation
true              // Boolean
null              // Null

2.2 Variable Assignment

Assigning values to variables for later use in expressions. Operator: =

#myVariable      = 'Some Value'
#anotherVariable = 42
#user            = #currentUser()

2.3 Expression Templating

Combining literals and variables to create dynamic strings. Operator: +

#name      = 'John Doe'
#greeting  = 'Hello, ' + #name + '!'

2.4 Data Types

Different types of data that can be used in tSM SpEL. Commonly used data types are:

#myString       = 'Hello world!'                            // String
#myInteger      = 123                                       // Integer
#myDouble       = 123.45                                    // Double
#myBoolean      = true                                      // Boolean
#myList         = ['order', 'ticket', 'process']            // List
#myMap          = {'customerName':'Alpha', 'employees':100} // Map
#myObject       = @user.user.get(#currentUser())            // Object
#myJsonNode     = '{"name":"John","age":30}'.toJsonNode()   // JsonNode
#mySpreadsheet  = #spreadsheet                              // Spreadsheet
#myDate         = #now()                                    // Date
#myUUID         = #randomUUID()                             // UUID
#myNull         = null                                      // Null

Note Under the hood, tSM SpEL uses Java and Kotlin. For example, String is a Java String, List is a Java List, and so on. For security reasons, you cannot use any Java class or method directly in SpEL. We only allow a limited set of classes and methods that are safe to use in the tSM environment. However, general rules of Java apply, so you can consult Java and Kotlin documentation for more details on behaviour of standard classes and methods.

2.5 Basic Operations & Operators

2.5.1 Arithmetic

Used to perform arithmetic operations. Operators available: +, -, *, ^, / or div, % or mod

#addition        = 1 + 2           // 3
#subtraction     = 5 - 2           // 3
#multiplication  = 3 * 4           // 12
#exponentiation  = 2 ^ 3           // 8
#division        = 10 / 3          // 5   —or— 10 div 2
#modulus         = 10 % 3          // 1   —or— 10 mod 3

2.5.2 Relational

Used to compare two values. Operators available: == or eq, != or ne, < or lt, > or gt, <= or le, >= or ge

#isEqual             = 1 == 1         // true   —or— 1 eq 1
#isNotEqual          = 1 != 2         // true   —or— 1 ne 2
#isLessThan          = 1 < 2          // true   —or— 1 lt 2
#isGreaterThan       = 2 > 1          // true   —or— 2 gt 1
#isLessThanOrEqual   = 1 <= 1         // true   —or— 1 le 1
#isGreaterThanOrEqual= 2 >= 1         // true   —or— 2 ge 1
#isEqualString       = 'is' == 'is'
#isNotEqualString    = 'is' != 'not'

2.5.3 Logical

Used to perform logical operations. Operators available: && or and, || or or, ! or not

#logicalAnd   = true && false    // false   —or— true and false
#logicalOr    = true || false    // true    —or— true or false
#logicalNot   = !true            // false   —or— not true

2.6 Data Access

2.6.1 Property / Key Access

Accessing values in 'Maps' and 'Objects' by their 'Keys' or 'Properties'. Operators: ., ['key']

#myMap = {'name':'Your Company', 'address':{'street':'Na padesátém','city':'Prague'}}
#myMap.name              // "Your Company"      —or— #myMap['name']
#myMap.address.street    // "Na padesátém"      —or— #myMap['address']['street']

2.6.2 List Item Access

Accessing 'Items' in a 'List' by their index. Operator: [index]

#myList = {'order', 'task', 'process'}
#myList[0]                // "order"

2.6.3 Collection Selection & Projection

Collection Selection: Selecting items from a collection based on specific criteria. Operator: .?[condition]

Collection Projection: Projecting specific properties of items in a collection. Operator: .![key]

// Collection Selection and Collection Projection
// You have this 'List of Maps', for example:
#services = [{'name': 'Int', 'price': 10}, {'name': 'TV', 'price': 15}, {'name': 'Phone', 'price': 5}]

// Selecting services with price greater than 9
#sel = #services.?[price > 9] // [{'name': 'Int', 'price': 10}, {'name': 'TV', 'price': 15}]; Collection Selection

// Projecting the names of the selected services
#serviceNames = #sel.![name] // ['Int', 'TV']; Collection Projection

// Or you can combine them in one step
#selectedServiceNames = #services.?[price > 10].![name]  // Combined selection and projection
#selectedServiceNames  // ['TV']    

Tip As the result of the script is the last expression, to evaluate only #sel, select the script up to the row and hit Ctrl + Enter. Or you can use debugger to see partial results.

2.7 Flow Control & Ternary Operator

2.7.1 Condition-based Flow Control

Controlling the flow of execution in expressions.

Condition-based flow control operators allow you to execute expressions based on specific conditions, providing a way to handle different scenarios dynamically. Operators available:

  • #if(cond1).then(exp1).elseif(cond2).then(exp2).else(exp3)
  • #match(var1).when(valueOfVar1,exp1).else(exp2)
  • #case().when(cond1,exp1).else(exp2)
// If-then-elseif-then-else Flow Control
#total      = 99

#if(#total > 200)
   .then(#discount = 0.20)
.elseif(#total > 100)
   .then(#discount = 0.10 )
.else(#discount = 0.05 )           // 0.05

// Match-when-else Flow Control
#technologyAvailable = '5G'
#match(#technologyAvailable)
    .when('5G',#provision = 'mobile')
    .else(#provision = 'fix')      // 'mobile'

2.7.2 Block-based Flow Control

Block-based flow control operators allow you to group expressions and execute them together, making it easier to manage complex logic. Operators available:

  • #with(exp1,exp2).do(exp3)
  • #do(exp1, exp2)
#with(
    #orderTotal      = 250,
    #orderDiscount   = 0.10,
    #discountedTotal = #orderTotal - (#orderTotal * #orderDiscount)
).do(
    #orderType = #case()
         .when(#orderTotal > 100, 'Large Order')
         .else('Small Order')
)                                           // 'Large Order'

2.7.3 Iteration

Iteration operators allow you to loop through collections and execute expressions for each item, enabling repetitive tasks to be handled efficiently.
Available operator:

.forEach(var1, exp1, exp2, ...)
  • var1 – the name of the variable that will hold each item during the iteration
  • expN – one or more expressions to evaluate for each item

Note: During the loop, a special variable #index is also available. This represents the zero-based index of the current item within the collection.

Example 1: Calculating squares

#numbers  = [1, 2, 3, 4]
#squares  = new java.util.ArrayList()
#numbers.forEach(#num, #squares.add(#num * #num))
#squares  // [1, 4, 9, 16]

Example 2: Adding indexed strings

#fruits  = ['apple', 'banana', 'cherry']
#results = new java.util.ArrayList()
#fruits.forEach(#item, #results.add('Item ' + #index + ': ' + #item))
#results  // ['Item 0: apple', 'Item 1: banana', 'Item 2: cherry']

2.7.4 Ternary

The ternary operator is a concise way to perform conditional logic in place of an if-then-else statement. It simplifies the code and enhances readability, especially for simple conditions.

#orderTotal = 150
#orderType  = #orderTotal > 100 ? 'Large Order' : 'Small Order' // 'Large Order'

2.8 Null Safety & Error handling

2.8.1 Null Safety Operator

The null safety operator helps prevent NullPointerExceptions by allowing safe navigation through potential null references.

// Safe navigation through potential null references - Null Safety Operator
#order = {'customer': null}
#customerName = #order.customer?.name  // null if customer is null

2.8.2 Default Value Setting - Elvis Operator

The Elvis operator provides a shorthand for assigning default values when expressions evaluate to null.

// Default value assignment using Elvis Operator
#customerName = null
#displayName = #customerName ?: 'Unknown Customer'  // 'Unknown Customer'

2.8.3 Error Catching

The try-catch construct allows handling exceptions within SpEL expressions.

// Exception handling using Try-catch Flow Control
#result = #try(
    #riskyOperation()
).catch(
    'Error occurred'
)

2.9 Built-in Functions & Type Extensions

There are two kinds of built-in functions:

  1. Type extensions – methods that extend everyday data types such as strings, numbers, dates, lists, sets and maps. They read naturally:

    'Hello'.uppercase()        // "HELLO"
    [1,2,3].sum()              // 6 (example only)
    
  2. Standalone functions – utilities that are not tied to any specific value and are invoked with the # prefix:

    #now()                     // current date-time  
    #randomUUID()              // random unique ID e.g. 'aee4b129-0680-4aff-8b7a-af2e83472336'  
    #if(#now().formatted('HH') == '18').then('Yes').else('No') // 'Yes' if it's currently 18:00 to 18:59, else 'No'
    

See 07_SpEL_builtin_functions.md for the full catalogue.

2.10 Context Variables

Context variables provide access to various aspects and provide informarmation about the current execution context in tSM.

// Context Variables
#orderProductCodes = #order.productCodes() // Get list of product codes processed within order
#completedTask = #task.complete()          // Finish the current task and move process forward
#processInfo = #execution.processInstance  // Get information about running Process
#ticketStatus = #ticket.status             // Retrieves status of current running ticket

2.11 tSM Services

tSM services are powerful tools that allow you to interact with various functionalities within the tSM platform. Using them you can manipulate every entity of the tSM platform, create various API calls and use them as part of integration with another platfrom. To these there are about 1500 methods of tSM services available, so they can't be listed here, but we will look at them deeper in chapter III. Standard syntax of tSM Service is @tsmService.tsmMethod.

#currentUser = @user.user.get(#currentUser()).name // Your full name, e.g.: 'Adam Sandler' 

#customer    = @tsmDatabaseClient
                 .query("select id,key from crm.crmt_customer")
                 .execute()
                 .first()
                 .id // Customers UUID, e.g 'f05bd47a-f43a-44e1-bf1e-79823eeff891'

#account     = @crm.person.find({customerId: #customer}).first().phones // Phones of person associated with customer

Hint: To explore tSM Services, you can leverage the autocomplete option of the tSM Console or open the 'tSM Public API' item in the main menu of the tSM Platform. Here, you will find the Swagger documentation for all available services, as well as Public API operations.

2.13 Advanced Expressions & Constructs

2.13.1 Class Expressions

Class expressions allow you to access static methods and fields of a class directly within SpEL.

#sqrtResult = T(java.lang.Math).sqrt(16)      // 4

2.13.2 Regular Expressions

#matches = '192.168.0.2'
              .matches('^(25[0-5]|2[0-4][0-9]|1\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4][0-9]|1\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4][0-9]|1\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4][0-9]|1\d{2}|[1-9]?\d)$')
              // true

3 Complete Script Example

// Primitive values
#orderTotal        = 250
#orderDiscount     = 0.10
#orderStatus       = 'Pending'
#notificationSent  = false
#priority          = false
#customerName      = 'John Doe'

// Collections
#adminEmails       = ['admin1@telco.com', 'admin2@telco.com']
#services          = [
    {'name':'Internet','price':100},
    {'name':'TV','price':150},
    {'name':'Phone','price':50}
]

// Calculations
#discountedTotal   = #orderTotal - (#orderTotal * #orderDiscount)
#isLargeOrder      = #orderTotal > 100

// Ternary & Elvis
#orderType         = #isLargeOrder ? 'Large Order' : 'Small Order'
#isPriority        = #isLargeOrder ? true : false
#customerNameSafe  = #customerName ?: 'Unknown Customer'

// Collection ops
#selectedServices  = #services.?[price > 50]
#serviceNames      = #services.![name]

// Flow control & service call
#sendNotification  = #if(#isPriority and !#notificationSent).then(
    #adminEmails.forEach(
        #email,
        @notificationPublicService.sendNotification({
            "templateCode": "TaskAssigned",
            "ownerId": #currentUser(),
            "ownerType": "Order",
            "notificationTo": [
                {"ownerId": #currentUser(), "ownerType": "User"},
                {"email"  : "peter.yan@google.com"}
            ],
            "data": { "order": #order, "task": #task }
        })
    ),
    #notificationSent = true
).else('Notification not needed')

#result = {
    'Total Amount'        : #orderTotal.toString(),
    'Discounted Total'    : #discountedTotal.toString(),
    'Order Status'        : #orderStatus,
    'Order Type'          : #orderType,
    'Priority Order'      : #isPriority,
    'Customer Name'       : #customerNameSafe,
    'Services'            : #serviceNames.toString(),
    'Selected Services'   : #selectedServices.toString(),
    'Notification Sent'   : #notificationSent
}