Reference
What a target expression may contain.
Literals, operators, the Java classes an expression can name, and the methods those values expose. Everything on this page is checked by the validator when the workbook loads; anything not on it is refused with a row number.
trade.quantity * price.settle > 0
? trade.tradeId.toUpperCase()
: 'REJECTED-' + LocalDate.now().year
Source types
The ten types a source field can carry.
Declared once, in the Source type column. Each source field names one of these, so an expression works with a real value of that class: it compares, converts and exposes the methods further down this page.
trueA flag. Drives ternaries and if/else directly. It has no methods of its own.
'ABC123'Identifiers, codes and names. Fifteen methods.
42Whole numbers up to about two billion, such as a date held as digits.
42LWhole numbers beyond the Integer range, such as record ids.
7.5dFloating-point quantities and prices, such as quantity and tradePrice.
2.50Exact decimals for money. Every plain decimal literal is one.
2026-09-18A calendar date with no time of day. Eighteen methods.
2026-09-18T14:30:00Date and time with no zone, such as settlementDate.
14:30:00A time of day: hour, minute and second.
java.sql.TimestampHow a database driver delivers tradeExecutionDateTime. toLocalDateTime() turns it into a date and time.
Property access
Walking the source tree.
Full path from the root, or the short name inside a bound section. Once a target node is bound to a source node, the expressions beneath it start there. Reference variables work the same way.
| S1 | S2 | S3 | S4 | Type |
|---|---|---|---|---|
| trade | ||||
| tradeId | String | |||
| tradeExecutionDateTime | Timestamp | |||
| product | ||||
| productType | String | |||
| listedDerivative | ||||
| buySell | String | |||
| quantity | Double | |||
| settlementDate | LocalDateTime | |||
| tradePrice | Double | |||
| instrument |
- a String, one level down
- trade.tradeId
- one node deeper
- trade.product.productType
- a Double four levels down
- trade.product.listedDerivative.quantity
- a Timestamp, read as a date
- trade.tradeExecutionDateTime.toLocalDateTime().year
- LocalDateTime to LocalDate
- trade.product.listedDerivative.settlementDate.toLocalDate()
- two leaves in one expression
- trade.product.listedDerivative.tradePrice * trade.product.listedDerivative.quantity
- safe navigation with a default
- trade.product?.listedDerivative?.instrument?.exchange ?: 'UNKNOWN'
- inside a section bound to trade.product.listedDerivative
- listedDerivative.quantity
instrument.exchange - a reference variable
- counterparty?.legalEntityId ?: counterparty?.partyId
Literals
The values you can write down.
A plain decimal literal is exact, so money math stays exact by default: 10 / 4 is 2.5 and 0.1 + 0.2 is 0.3. Add d or f for a Double or a Float.
Numbers
- Integer
- 42
- Long
- 42L
- exact decimal
- 3.14
- Double, Float
- 3.14d 3.14f
- readable digits
- 1_000_000
Strings
- either quote style
- 'single' "double"
- escapes work in both
- 'tab\tnew line\n'
- convert with +
- '' + trade.quantity
There is no string interpolation. Build text with +, or prefix an empty string to convert a value.
Dates
- LocalDate
- '2026-08-31'
- LocalDateTime
- '2026-08-31T14:30:00'
A single-quoted string in exactly that shape becomes a real date value, so it compares and exposes getters. An invalid date is a parse error. Double quotes keep it a string.
Booleans and null
- the three words
- true false null
Truthiness applies wherever a condition is expected: null, 0, an empty string, list or map are false. Everything else is true.
Lists
- a list literal
- [1, 2, 3] []
- from the front, from the end
- legs[0] legs[-1]
A list literal, or a list from the source. first() and last() work as well.
Maps
- a map literal
- [a: 1, 'b': 2] [:]
- read a value
- attrs['venue'] attrs.venue
Keys are strings. Read with brackets or with a dot, so a map reads like a bean.
Operators
Arithmetic, comparison, logic.
Numbers compare by value, so 1 == 1.0. Dates compare too, including against a date literal. Logic short-circuits, with truthiness on each side.
| Family | Operators | Notes |
|---|---|---|
| Arithmetic | + - * / % ** | 10 / 4 is 2.5, an exact decimal. Mixing in a double promotes to double. + on a string concatenates. |
| Comparison | == != < <= > >= | By value for numbers; dates compare directly: tradeDate > '2026-01-01'. |
| Logic | && || ! | Short-circuit. 0, '', [] and null count as false. |
| Membership | x in [1, 2, 3] | True when x equals an element, using the same comparison as ==. |
| Property access | a.b.c a?.b map.key | Getter, then public field, then map key, nested to any depth. ?. yields null instead of failing on a null target. |
| Indexing | list[0] list[-1] map['k'] list?[0] | Negative indexes count from the end. ?[ guards a null target and is one token: write c ? [1] : 2 with the space. |
Conditionals
Three ways to choose a value.
Every one of them is an expression, so it yields a value. There are no statements: no assignment, no loops, no return, no blocks. Nothing is stored between rows.
Ternary
qty > 0 ? 'BUY' : 'SELL'
The everyday form. The condition uses truthiness, so qty ? 'has qty' : 'none' works as well.
Elvis
trade.venue ?: 'UNKNOWN' nobody?.name ?: '-'
Keeps the left side when it is truthy, otherwise the right. With safe navigation it defaults a whole chain in one line.
If / else
if (qty > 100) 'large' else if (qty > 10) 'medium' else 'small'
An expression, not a statement, so it always has an else. Compose with parentheses: (if (c) 1 else 2) + 3.
- Safe navigation guards its own step only:
null?[0].namestill fails; writenull?[0]?.name. - A condition is any value: null, 0, an empty string or an empty list count as false, everything else as true.
Classes
Nine classes, and exactly what each exposes statically.
A class name resolves only if it is on this list. What is listed is what a target expression can call on these classes; anything else on them is not supported.
| Class | Static methods |
|---|---|
| Integer | valueOf(String) max(Integer, Integer) min(Integer, Integer) |
| Long | valueOf(String) max(Long, Long) min(Long, Long) |
| Double | valueOf(String) max(Number, Number) min(Number, Number) |
| Float | valueOf(String) max(Number, Number) min(Number, Number) |
| String | valueOf(String) |
| Boolean | valueOf(String) |
| LocalDate | now() of(year, month, day) |
| LocalDateTime | now() of(year, month, day, hour, minute, second) |
| Math | abs(Number) max(Number, Number) min(Number, Number) |
- Only listed names resolve.
System.exit(0)fails with Unknown variable: System. - Signatures match the argument types.
Integer.valueOf('42')runs; the same call with a number is not supported. - No static fields at all. Math.PI, Integer.MAX_VALUE and LocalDate.MIN are not supported.
- Overloads are chosen Java-style. Exact match, then widening, then narrowing as a last resort. Math.abs with a Long returns a Long.
- Two refusals. A name that does not exist is No such static method; a real method outside the list is Unsupported static method.
Instance methods
What a value can do, by family.
Operators and comparisons are separate from methods: x.abs() is not supported, while -x and x < 0 work. A conversion hands back a value with its own list.
String 15 signatures
endsWith(String) · indexOf(String) · isBlank() · lastIndexOf(String) · length() · matches(String) · replaceAll(String, String) · replaceFirst(String, String) · split(String) · startsWith(String) · substring(int) · substring(int, int) · toLowerCase() · toUpperCase() · trim()
contains, charAt, isEmpty, equals, strip, another arity or overload, and getters reached as properties are not supported.
Numbers conversions
Integer: doubleValue() · longValue()
Long: doubleValue() · intValue()
Double: intValue() · longValue()
Float: intValue() · longValue()
BigDecimal: doubleValue() · intValue() · longValue() · precision() · scale()
BigInteger: doubleValue() · intValue() · longValue()
Boolean has no methods. Use operators, elvis, if/else, or '' + b.
LocalDate 18 signatures
atStartOfDay() · getDayOfMonth() · getDayOfWeek() · getDayOfYear() · getMonth() · getMonthValue() · getYear() · plusDays(long) · plusWeeks(long) · plusMonths(long) · plusYears(long) · minusDays(long) · minusWeeks(long) · minusMonths(long) · minusYears(long) · withDayOfMonth(int) · withMonth(int) · withYear(int)
The getters are also properties: d.year, d.dayOfWeek. Dates compare directly with <, > and ==.
LocalDateTime, LocalTime, Timestamp
LocalDateTime: getDayOfMonth() · getMonth() · getYear() · getHour() · getMinute() · getSecond() · toLocalDate() · toLocalTime()
LocalTime: getHour() · getMinute() · getSecond()
Timestamp: toLocalDateTime()
// FRIDAY tradeDate.plusDays(2).dayOfWeek // a timestamp, compared as a date ts.toLocalDateTime().toLocalDate() < '2026-01-01' // first of the month LocalDate.of(dt.year, dt.month.value, 1)
List and array 2 signatures
first() · last()
Any list or array from the source. The result keeps its own type, so a String comes back with String's methods and a bean with its properties. An empty list has no first(); guard it with a truth check.
legs.first().price legs.last().settlementDate.toLocalDate() legs.first().quantity + legs.last().quantity legs ? legs.first().price : null client?.parties?[0] client?.parties?.first()
Map 3 signatures
size() · isEmpty() · containsKey(key)
A map is not a list. It keeps these three and reads a value with map['key'] or map.key.
Collections and names
Beans, variables, functions and constants.
Root names are the variables supplied with the row. Properties resolve by getter, then public field, then map key, so mixed graphs work.
Beans and variables
order.customer.address.city trade.legs[0].price it.name
Custom functions
lookupRate('CAD', 15)
getExchange(instrument.exchange)
A bare call with a declared signature on the Functions sheet. Arity and argument types are checked like any other call.
Constants
APP_NAME + '-' + REGION
An UPPER_CASE name bound to a string. A constant is always a string.
From a real workbook
- Amount · two leaves and a ternary
- listedDerivative.quantity * (listedDerivative.buySell == 'Sell' ? -1 : 1)
- Exchange · a lookup with the raw code as fallback
- getExchange(instrument.exchange) ?: instrument.exchange
- Counterparty · safe navigation on a reference variable
- counterparty?.legalEntityId ?: counterparty?.partyId
- CommodType · nested ternaries
- security == null ? null : security.futuresCategoryType == 'Precious Metal' ? 'PM' : 'BM'
Guard rails
Every error names the field, before the row runs.
Given the variable names and their types, the validator checks every chain statically: names, properties, methods, arity, the class and method lists, and literal argument types. Whatever it could not know, an untyped variable or a map key, is caught at evaluation with the same wording, per field, not per record.
Unknown variable: tradeId2
No such property 'venue' on instrument
No such method 'round' with 0 argument(s) on Double
Unsupported method: collect
Unsupported static method: Integer.parseInt
Division by literal zero
Cannot get property 'contractSize' on null
Cannot compare Integer with String
Unsupported method: abs
Unknown constant: MAX_SIZE
Error invoking getExchange: ...
Every method an expression can call is one listed on this page. A class outside the table above cannot be named, and a constant is always a string. See the refusal in a real run.