How do you use SpEL programmatically? Walk through SpelExpressionParser and EvaluationContext, including #root and #this.
answer
- SpelExpressionParser -> parseExpression -> Expression
- getValue(context, root)
- StandardEvaluationContext (full) vs SimpleEvaluationContext (safe)
- #root = root object, #this = current element
- cache parsed Expressions
basics
~20 sCreate a SpelExpressionParser, call parseExpression(str) to get an Expression, then getValue(). To evaluate against data and variables you pass an EvaluationContext (e.g. StandardEvaluationContext) holding a root object and #variables. #root is the root object; #this is the current item during iteration.
solid answer
~40 sProgrammatically you use `ExpressionParser parser = new SpelExpressionParser();` then `Expression exp = parser.parseExpression("name.toUpperCase()")`. Evaluate with `exp.getValue()` for context-free literals, or `exp.getValue(context, rootObject)` / `exp.getValue(context)` when you have data. The `EvaluationContext` holds the evaluation state: the **root object** (default target for property/method resolution), named **variables** (`#var`, set via `setVariable`), registered functions, property accessors, and type/bean resolvers. `StandardEvaluationContext` is the full-featured, reflection-powered context; `SimpleEvaluationContext` is a locked-down subset for untrusted input. Inside the expression, **`#root`** always refers to the context's root object, while **`#this`** refers to the *current* evaluation object — normally the root, but rebound to each element inside selection/projection (`.?[]`/`.![]`). You can also set values back: `exp.setValue(context, newValue)` for writable expressions.
code
java · 20 linesExpressionParser parser = new SpelExpressionParser();
// Parse once, reuse (thread-safe)
Expression exp = parser.parseExpression("users.?[#this.age > #root.minAge].![name]");
record User(String name, int age) {}
record Query(int minAge, List<User> users) {}
Query q = new Query(18, List.of(new User("Ann", 34), new User("Bob", 17)));
// Trusted -> StandardEvaluationContext; root object = q
StandardEvaluationContext ctx = new StandardEvaluationContext(q);
ctx.setVariable("tag", "X");
@SuppressWarnings("unchecked")
List<String> names = (List<String>) exp.getValue(ctx); // -> [Ann]
// Untrusted input? Lock it down:
EvaluationContext safe = SimpleEvaluationContext
.forReadOnlyDataBinding().build(); // no T(), no constructors, no beansgo deeper
Know the basic parser.parseExpression(...).getValue() flow and that an EvaluationContext supplies data.
Explain root object, variables, and #root; use getValue overloads correctly.
Contrast StandardEvaluationContext vs SimpleEvaluationContext, explain #root vs #this rebinding, caching, and the BeanFactory-backed context behind @Value.
Drive policy on safe evaluation of dynamic expressions, compiled-mode trade-offs, and where programmatic SpEL is justified versus plain code.
## The programmatic pipeline SpEL is usable with zero Spring container: 1. **Parser**: `ExpressionParser parser = new SpelExpressionParser();` (`SpelExpressionParser` is the only production `ExpressionParser`). Optionally configure it with `SpelParserConfiguration` (auto-grow collections, compiler mode). 2. **Parse**: `Expression exp = parser.parseExpression("...");` — turns the string into a reusable, thread-safe `Expression`. Parsing is relatively expensive, so **cache parsed `Expression`s**, not the strings. 3. **Evaluate**: `exp.getValue(...)`. Overloads: - `getValue()` — no context, only literals/`T()`/self-contained expressions. - `getValue(Object root)` — evaluate against a root with a default context. - `getValue(EvaluationContext ctx)` / `getValue(EvaluationContext ctx, Object root)` — full control. - `getValue(..., Class<T> desiredType)` — with type conversion via the `ConversionService`. ## EvaluationContext — the state holder `EvaluationContext` supplies everything evaluation needs: - **Root object** — the implicit target for unqualified property/method access (`name` means `root.getName()`). - **Variables** — `ctx.setVariable("count", 5)` then reference `#count`. - **Functions** — register a `Method` as `#fn(...)`. - **Property accessors / type locator / bean resolver / ConversionService / OperatorOverloader**. ### Two implementations - **`StandardEvaluationContext`**: full power — reflection-based property/method resolution, `T()` type references, constructor calls, bean references. Use for trusted expressions. - **`SimpleEvaluationContext`**: a deliberately restricted context (builder: `SimpleEvaluationContext.forReadOnlyDataBinding().build()` or `forPropertyAccessors(...)`). It disables `T()`, constructors, and bean references, so it is the safe choice when the expression string may be influenced by users. ## `#root` vs `#this` Both are built-in reserved variables: - **`#root`** — always the **root object** of the context, no matter how deep you are. - **`#this`** — the **current** object under evaluation. At the top level it equals the root, but inside selection/projection (`.?[]`, `.![]`) it is rebound to each element. So inside a projection you use `#this` for the element and `#root` to still reach the original root. Example: `#root.users.?[#this.age > #root.minAge]` — `#this` is each user, `#root` is the outer object providing `minAge`. ## Writing values Expressions can be assignable: `exp.setValue(ctx, value)` writes through a property path (`isWritable(ctx)` tells you if it's allowed). This is used by data binding. ## In Spring itself When Spring evaluates `@Value("#{...}")`, `StandardBeanExpressionResolver` builds a `StandardEvaluationContext` whose bean resolver points at the `BeanFactory` (enabling `@beanName`) and whose root exposes the `BeanExpressionContext`. That is machinery you rarely touch directly, but it explains the available features. ## Gotchas - **Cache parsed expressions** — re-parsing per call is wasteful. - **Choose the right context** — `StandardEvaluationContext` over untrusted input is a remote-code-execution risk (`T()`/constructors). Use `SimpleEvaluationContext`. - **Thread-safety** — `Expression` is thread-safe; `StandardEvaluationContext` is generally reusable but avoid mutating shared variables concurrently. - **`#this` misuse** — outside selection/projection it just equals the root; don't rely on it changing elsewhere.
- Why prefer SimpleEvaluationContext over StandardEvaluationContext for user-supplied expressions?StandardEvaluationContext allows T() type references, constructor calls, and bean access, which lets a malicious expression execute arbitrary code (e.g. T(Runtime).exec). SimpleEvaluationContext disables those, restricting evaluation to safe property access and basic operators.
- What is the performance concern with SpelExpressionParser, and how do you address it?Parsing a string into an Expression is comparatively costly. Parse once and cache the reusable, thread-safe Expression object; optionally enable compiled mode via SpelParserConfiguration for hot paths. Re-parsing on every evaluation is the common mistake.
saying these in an interview costs you the question
- Saying #this always equals #root (it is rebound in selection/projection)
- Using StandardEvaluationContext for untrusted input
- Re-parsing the expression string on every call
- Thinking EvaluationContext is required even for pure literals