> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alginte.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SpEL expressions

> The expression language of the Streams builder: roots and variables, type-aware editing, calling Java, and custom functions.

Wherever a processor needs logic — a filter predicate, a mapping, an
aggregation step — you write a **Spring Expression Language (SpEL)**
expression. The editor is type-aware: it knows the key and value types at that
point in the topology and gives you completions, live validation, and
highlighting to match.

## Roots: `key` and `value`

In most expressions two root identifiers are in scope:

* `key` — the record key
* `value` — the record value

```text theme={null}
value['amount'] > 100          // filter on a JSON value
value.toUpperCase()            // mapValues on a String value
```

Some operators expose a different scope, with extra variables (prefixed `#`,
the standard SpEL convention):

| Expression                                   | Roots in scope                             | Variables                           |
| -------------------------------------------- | ------------------------------------------ | ----------------------------------- |
| Most operators (map, filter, selectKey, …)   | `key`, `value`                             | —                                   |
| Aggregate — initializer                      | `key`, `value`                             | —                                   |
| Aggregate — adder / subtractor (and cogroup) | `key` (grouping key), `value` (new record) | `#aggValue` — the current aggregate |
| Reduce — adder / subtractor                  | `value` (new record)                       | `#aggValue` — the accumulated value |
| Join — value joiner                          | —                                          | `#leftValue`, `#rightValue`         |
| Foreign-key join — key extractor             | `value`                                    | —                                   |

The editor highlights the identifiers that are valid in the field you're
editing, and the validator rejects out-of-scope ones with a hint (e.g.
`Unknown root identifier: aggValue. Use key, value or #aggValue.`).

```text theme={null}
#aggValue + value['qty']               // aggregate adder over JSON records
#leftValue['name'] + ' / ' + #rightValue['city']   // join value joiner
```

## Types drive the editor

What you can do with `key` and `value` depends on their configured types
(the stream's default serdes, per-node overrides, or the editor's type hints):

* **JSON / JSON Schema / Avro / Protobuf** — map-style access:
  `value['field']`, `value.containsKey('field')`. String methods are not
  available on the record itself.
* **String** — string methods: `value.length()`, `value.contains('x')`,
  `value.toLowerCase()`. No map-style access.
* **Numbers (Integer, Long, Float, Double, Short)** — numeric methods and
  arithmetic.
* **Bytes** — raw byte access.

Completions follow the same rules, including for chained calls — after
`value.someCall().` the editor infers the intermediate type and suggests only
what fits.

<Note>
  The editor box is resizable — drag its corner to make room for longer
  expressions.
</Note>

## Producing multiple values

`flatMap` / `flatMapValues` emit several records from one input. Return an
**inline list literal**:

```text theme={null}
{'a','b','c'}                          // three output values
value['tags']                          // or any expression yielding a list
```

## Calling Java

Static calls on a small allowlist of safe types are available via `T(...)`:
`Math`, the numeric box types, `String`, `Map`/`List`, `UUID`, and Kafka's
`Bytes`.

```text theme={null}
T(Math).max(value['a'], value['b'])
T(java.util.UUID).randomUUID().toString()
```

<Warning>
  Expressions are deliberately sandboxed: `T(...)` refuses everything outside
  the allowlist (no `Runtime`, `System`, `ProcessBuilder`, …) and the `new`
  operator is blocked entirely — use inline list literals `{'a','b'}` where
  you would have constructed an array. The editor won't suggest blocked
  constructs, and the validator rejects them.
</Warning>

## Custom functions

Operators can extend the vocabulary **without recompiling Alginte**: register
public static Java methods (from a JAR on the classpath) under
`kafka.streams.transformation.functions`, and call them as `#name(args)` in
any expression — with full editor support.

```properties theme={null}
kafka.streams.transformation.functions.maskEmail=com.acme.util.Masks#maskEmail
```

```text theme={null}
#maskEmail(value['email'])
```

Resolution is fail-fast at startup: a missing class, a non-static method, or
an ambiguous overload aborts with a precise message instead of failing later
mid-stream.
