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.

truthBooleantrue

A flag. Drives ternaries and if/else directly. It has no methods of its own.

textString'ABC123'

Identifiers, codes and names. Fifteen methods.

numberInteger42

Whole numbers up to about two billion, such as a date held as digits.

numberLong42L

Whole numbers beyond the Integer range, such as record ids.

numberDouble7.5d

Floating-point quantities and prices, such as quantity and tradePrice.

numberBigDecimal2.50

Exact decimals for money. Every plain decimal literal is one.

date / timeLocalDate2026-09-18

A calendar date with no time of day. Eighteen methods.

date / timeLocalDateTime2026-09-18T14:30:00

Date and time with no zone, such as settlementDate.

date / timeLocalTime14:30:00

A time of day: hour, minute and second.

date / timeTimestampjava.sql.Timestamp

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

Source section, S1 to S4
S1S2S3S4Type
trade
tradeIdString
tradeExecutionDateTimeTimestamp
product
productTypeString
listedDerivative
buySellString
quantityDouble
settlementDateLocalDateTime
tradePriceDouble
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.

FamilyOperatorsNotes
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.
Membershipx in [1, 2, 3]True when x equals an element, using the same comparison as ==.
Property accessa.b.c a?.b map.keyGetter, then public field, then map key, nested to any depth. ?. yields null instead of failing on a null target.
Indexinglist[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].name still fails; write null?[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.

ClassStatic methods
IntegervalueOf(String) max(Integer, Integer) min(Integer, Integer)
LongvalueOf(String) max(Long, Long) min(Long, Long)
DoublevalueOf(String) max(Number, Number) min(Number, Number)
FloatvalueOf(String) max(Number, Number) min(Number, Number)
StringvalueOf(String)
BooleanvalueOf(String)
LocalDatenow() of(year, month, day)
LocalDateTimenow() of(year, month, day, hour, minute, second)
Mathabs(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.

Validator · before any data 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
Evaluator · per row 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.