Skip to content

Instantly share code, notes, and snippets.

@edudant
Created May 9, 2025 13:22
Show Gist options
  • Save edudant/3a982ed3557a58e19b44f33f7ad17e8a to your computer and use it in GitHub Desktop.
Save edudant/3a982ed3557a58e19b44f33f7ad17e8a to your computer and use it in GitHub Desktop.
sidebar_position title description
2
tSM SpEL – Quick Reference & Deep Dive
A single, authoritative guide to the tSM Spring Expression Language – from the very first keystroke in the console to sophisticated, production-grade scripts.

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.

If you are completely new to SpEL you can skim the Essentials in a Nutshell section first and then jump straight to the detailed syntax reference further below whenever you need the fine print.


1 Essentials in a Nutshell

1.1 SpEL Console Usage & Shortcuts

Shortcut Action
Ctrl + Space Open autocomplete suggestions.
Ctrl + Space twice Show a description of the highlighted suggestion.
Ctrl + Enter Evaluate the entire script.
Select text + Ctrl + Enter Evaluate only the selected expression.
Select documentation + Enter Insert example snippet of selected parameter.

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.

1.3 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

1.4 Variable Assignment

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

#myVariable     = 'Some Value',
#anotherVariable = 42,
#customer        = @customerPublicService

1.5 Expression Templating

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

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

1.6 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.

1.7 Basic Operations & Operators

1.7.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

1.7.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'

1.7.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

1.8 Data Access

1.8.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']

1.8.2 List Item Access

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

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

1.8.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 > 50].![name]  // Combined selection and projection
#selectedServiceNames  // ['Int', '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.

1.9 Flow Control & Ternary Operator

1.9.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'

1.9.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'

1.9.3 Iteration

Iteration operators

allow you to loop through collections and execute expressions for each item, providing a way to handle repetitive tasks. Operators available:

  • .forEach(var1, exp1, exp2)
#numbers  = [1, 2, 3, 4]
#squares  = []
#numbers.forEach(#num, #squares.add(#num * #num))   // [1, 4, 9, 16]

1.9.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'

1.10.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

1.10.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'

1.10.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'
)

1.11 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()              // e.g. "8e29…"  
    #if(#total > 100).then('Large')...
    

See 07_SpEL_builtin_functions.md for the full catalogue.

1.12 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

1.13 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()),

#customer    = @tsmDatabaseClient
                 .query("select id,key from crm.crmt_customer")
                 .execute()
                 .last()
                 .id,

#account     = @crm.person.geByCustomerId(#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.

1.14 Advanced Expressions & Constructs

1.14.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

1.14.2 Regular Expressions

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

1.15 Complete Script Example

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

// Collections
#adminEmails       = ['[email protected]', '[email protected]']
#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"  : "[email protected]"}
            ],
            "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
}

2 SpEL Syntax & Console Deep Dive

2.1 Opening the Console

Mode How to open When to use
Without context In the main left menu search for spel and select SpEL Console. Quick experiments, standalone utilities.
With context Open 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.

2.2 Autocomplete Cheat-sheet

  • Ctrl + Space – suggestions for partially typed items.
  • @ then Ctrl + Space – list all tSM services.
  • # then Ctrl + Space – list standalone functions, variables and flow operators.
  • Double Ctrl + Space – show a description of the selected suggestion.
  • Enter inside () – insert example arguments for a method.

2.3 Running Scripts & Expressions

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

Using these shortcuts dramatically speeds up your workflow & reduces typos.

2.4 Script Structure

A script is a sequence of expressions, each separated by a comma. You can write those expressions:

  • Inline, without delimiters – the most common form.
  • Inside { … } or [ … ] – helpful when you embed a script inside another expression.
#first  = 'Hello ',
#second = 'World',
#third  = #first + #second              // "Hello World"

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.5 Expression Separation (Advanced)

Every expression except the last must end with a comma:

#one  = 'A',               // comma
#two  = 'B',               // comma
#three = #one + #two       // no comma – this is the last expression

2.6 Assigning Values & Changing Types

Use = to assign. Re-assigning the same variable implicitly changes its data type and overwrites the previous value.

#value = 3,
#value = 'now I am a string'

2.7 Calling Methods & Services

  • Standalone functions & operators begin with # when they are the first item in a chain.

    #now()
    #if(condition).then(expr1).else(expr2)
    
  • Chained methods start with ..

    'some text'.uppercase()
    
  • Services start with @ and expose their own methods.

    @customerPublicService.findCustomer(#idOrKey)
    

2.8 Block Bodies

Every flow-control/operator body is enclosed in ():

#with(body).do(otherBody)

2.9 Using {}, [], ()

  • Curly braces {} – optional script delimiter or to create an ad-hoc block when nesting scripts.
  • Square brackets [] – list/array literals and positional access.
  • Parentheses () – method arguments, grouping sub-expressions.

2.10 Reference Reading Checklist

  1. Operators & syntax – see Sections 1.7-1.10 above.
  2. Built-in helpers  – full list in 07_SpEL_builtin_functions.md.
  3. All tSM services  – swagger docs under tSM → Public API.
  4. Console shortcuts  – Section 2.1 & 2.2.

Happy scripting — and may your expressions always evaluate exactly as expected!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment