Skip to main content

FEEL Overview

Friendly Enough Expression Language (FEEL) is the standardised expression language defined by the OMG DMN specification. It is designed to be executable while remaining readable for business users, and is the language QuantumBPM uses for every dynamic value in both DMN decisions and BPMN processes.

💡 Try FEEL in the browser at feel-playground.quantumbpm.com — the same LSP server that runs in the modeler powers an in-browser editor with autocomplete, hover docs, signature help, and live diagnostics. Every expression you write gets a shareable URL, so you can send a colleague a link to a reproduction or a snippet to review without any setup on their end.

Introduction

FEEL provides a standard syntax for expressing decision and routing logic. Whether you are defining a complex decision-table rule, a simple literal calculation, or a sequence-flow condition, you are likely using FEEL. Its syntax draws inspiration from Java, JavaScript, and Excel formulas but is optimised for declarative business logic.

Key Characteristics

  • Side-Effect Free: FEEL expressions are purely functional. They calculate a value based on inputs and return a result without interacting with external systems or changing state. This makes FEEL safe to re-evaluate during workflow replay.
  • Rich Data Types: Built-in support for numbers, strings, booleans, dates, times, durations, lists, and contexts (key-value objects).
  • Three-Valued Logic: Logic operations like and/or handle true, false, and null (unknown), ensuring robust handling of missing data.
  • Type-Strict: No implicit conversions — the number 123 is not equal to the string "123". Conversions are always explicit.

Where is FEEL Used?

FEEL is used in two distinct places in QuantumBPM:

In DMN

  • Decision Tables — input entries (unary tests) and output entries.
  • Literal Expressions — the core logic of a decision node.
  • Boxed Expressions — context entries, list definitions, function invocations, and relations.
  • Input Data — defining constraints or default values.

In BPMN

  • Sequence-flow conditions (<conditionExpression>) — gateway routing.
  • I/O mappings (<quantum:ioMapping> source expressions on every task, sub-process, and call activity).
  • Script tasks — the entire body, with scriptFormat="feel".
  • Timer expressionstimeDuration, timeDate, timeCycle on timer events and timer boundaries.
  • Multi-instance loop characteristicsloopCardinality, inputCollection, outputElement, completionCondition.
  • Standard loop characteristicsloopCondition, loopMaximum.
  • Ad-hoc sub-processesactiveElementsCollection and completionCondition.
  • Message correlation<quantum:subscription correlationKey> on message events and receive tasks.
  • Conditional events — the <condition> of conditionalEventDefinition.
  • Business-rule task inputs — when invoking a DMN decision.
  • User-task assignment fields — assignees, candidate groups, candidate users.

In short: anywhere a value is computed from the running scope, it is a FEEL expression.

Examples

Simple Calculation

(Monthly Income * 12) + Bonus

Conditional Logic

if Age < 18 then "Minor" else "Adult"

Working with Dates

date("2023-12-25") + duration("P1D") // Returns 2023-12-26

List Operations

sum([10, 20, 30]) // Returns 60

List Filtering

orderItems[item.price > 100] // Items costing more than 100

Next Steps

Explore the detailed specifics of the language in the Syntax Guide, the standard library in Built-in Functions, and the editor integration in Tooling Support.


FAQ

What is FEEL?

FEEL stands for Friendly Enough Expression Language. It is the standardised expression language defined by the OMG DMN specification, designed to be readable by business users while being precise enough for automated execution. FEEL is purely functional and side-effect free — every expression returns a value, and evaluating the same expression twice with the same inputs always produces the same result. It has first-class support for numbers, strings, booleans, dates, times, durations, lists, and contexts (key-value objects), plus three-valued logic (true / false / null) for handling missing data cleanly.

What's the difference between FEEL and DMN?

DMN (Decision Model and Notation) is the modelling notation — boxes, decision tables, dependency graphs, hit policies. FEEL is the expression language used inside the DMN model: every cell of a decision table, every literal expression, every context entry is written in FEEL. People often conflate them because DMN introduced FEEL, but they're separate concerns: DMN tells you the shape of your decision logic, FEEL tells you how to write the individual expressions inside that logic. QuantumBPM also uses FEEL outside DMN — sequence-flow conditions, I/O mappings, timer expressions, and many more places in BPMN.

Where can I use FEEL in BPMN?

FEEL is the expression language for every dynamic value in a BPMN model. The most common places: sequence-flow conditionExpression (gateway routing), <quantum:ioMapping> source expressions on every task, script-task bodies, timer expressions (timeDuration, timeDate, timeCycle), <completionCondition> on multi-instance and ad-hoc sub-processes, <loopCardinality> and <inputCollection> for multi-instance, <loopCondition> for standard loops, activeElementsCollection on ad-hoc sub-processes, <quantum:subscription correlationKey> for message correlation, <conditionalEventDefinition> conditions, and user-task assignment fields. In short: anywhere a value is computed from the running scope, it is FEEL.

What's the difference between FEEL and JavaScript or JUEL?

FEEL is side-effect free by design — expressions return a value and cannot mutate state, perform I/O, or call out to the network. JavaScript and JUEL are imperative and can do all of those things. That makes FEEL safe to evaluate inside a deterministic workflow engine (re-evaluating a FEEL expression during replay always returns the same value), and impossible to use for things like "fetch this URL". FEEL also has different syntax — equality is = (not ==), if-then-else is an expression (not a statement), there are no semicolons, and built-in function names can contain spaces (string length, date and time). Use FEEL for routing, conditions, and value reshaping, use a service task with a worker for anything that touches the outside world.

Does QuantumBPM use full FEEL or S-FEEL?

Full FEEL. S-FEEL (Simple FEEL) is the unary-test subset of the language defined by the DMN spec for use in decision-table input entries — comparisons, intervals, lists, and not(). QuantumBPM implements both: full FEEL for every expression, and the unary-test subset for decision-table inputs, which is just FEEL parsed in unary-test mode. You can use the entire FEEL surface — for loops, if-then-else, list filters, function definitions, contexts, temporal types — anywhere the engine evaluates a FEEL expression.

Why does my FEEL expression sometimes need a leading `=`?

The leading = is a Zeebe-style convention adopted by QuantumBPM in BPMN extension attributes (<quantum:ioMapping> sources, sequence-flow conditions, multi-instance cardinality, correlation keys, and so on). It distinguishes an evaluated expression from a literal value: =customer.tier evaluates to the value of customer.tier, while customer.tier without the = would be treated as the literal string "customer.tier". Inside DMN literal expressions and decision-table cells the = is not required — those fields are always parsed as FEEL. Rule of thumb: if the surrounding XML attribute can hold either a literal value or an expression, you need the = to mark the expression.

What's the difference between `=` and `==` in FEEL?

FEEL uses = for equality and there is no ==. So customer.tier = "vip" is the FEEL way to check equality — coming from JavaScript or Java it looks like assignment, but FEEL has no assignment because it has no side effects. Inequality is != (same as most languages). Other comparisons (<, <=, >, >=) work as expected. The single-equals convention surprises people often enough that it's worth memorising up front, especially when porting expressions from other rule engines.

How do I write an if-then-else in FEEL?

if condition then value-if-true else value-if-false. It is an expression, not a statement, so it always produces a value and can be nested or composed: if age < 18 then "minor" else if age < 65 then "adult" else "senior". The else branch is required — there is no "if without else" in FEEL — because every expression must produce a value. If you want a value to be missing in one branch, use null explicitly: if shouldCharge then amount else null.

How do I filter a list in FEEL?

list[predicate] — square brackets after a list filter it by a predicate, with item as the implicit element variable. For example, orderItems[item.price > 100] returns the items whose price is over 100. You can also filter by index: orderItems[1] is the first item (FEEL is 1-indexed). Combined with other built-ins this gives compact pipelines: count(orderItems[item.shipped = true]) counts the shipped items. The same filter syntax works inside for loops, function arguments, and anywhere a list is expected.

Is FEEL type-strict?

Yes. FEEL does not perform implicit type conversions — the number 123 is not equal to the string "123", and comparisons across incompatible types either return null or raise an error depending on the operation. The same applies to message correlation: a subscriber whose correlation key resolves to 123 (number) will not match a publisher sending "123" (string). Use the built-ins number("123") and string(123) to convert explicitly when needed. The strict typing is intentional — implicit coercion is the source of many subtle decision-table bugs in other rule engines.

How does FEEL handle null and missing variables?

FEEL uses three-valued logic: every operation can return true, false, or null. Accessing a variable that does not exist returns null (not an error), and null propagates through expressions: null + 5 is null, null = 5 is false, null and true is null (unknown). This makes FEEL robust against missing data — an expression doesn't crash because one input is absent, it just returns null or false cleanly. To check for a missing value, you have two options: compare directly (value = null is true when the value is missing, value != null is true when it's present), or use the is() built-in (is(value, null) returns true when value is null). Both work, is() performs a strict identity check (same type and value), while = follows FEEL equality semantics.

What FEEL tooling does the QuantumBPM modeler provide?

The modeler embeds a full FEEL Language Server Protocol (LSP) implementation, surfacing language services live as you type. You get real-time syntax validation with line-and-column-accurate diagnostics, autocomplete for keywords, all ~80 standard built-in functions, and the variables actually in scope at the cursor position — including nested context paths and properties inside list filters (so typing customer. proposes the fields under customer, and typing items[item. proposes the per-item properties). Hover documentation provides signatures, parameter details, return types, and usage examples. Signature help shows the current argument while you type inside a function call, and semantic-token syntax highlighting marks keywords, built-ins, variables, and literals distinctly. The same LSP server runs the FEEL Playground for try-it-in-the-browser editing and is also available as a standalone binary for use in VS Code, Neovim, Emacs, or any LSP-compatible editor — see Tooling Support.

Is there a FEEL playground to try expressions without installing anything?

Yes — feel-playground.quantumbpm.com is an in-browser FEEL editor that runs the same LSP server the modeler uses. You get autocomplete, hover documentation, signature help, semantic syntax highlighting, and live diagnostics as you type, plus immediate evaluation of the result. Every expression has a shareable URL: paste the link into a chat or a ticket and the recipient sees the exact expression and inputs you were working with — useful for sharing snippets in code review, reproducing a tricky case for support, or teaching a teammate a particular FEEL pattern.