From AI prototype to credible information system.Explore Go Live Fintech
DMN / Decision Automation

FEEL Expression Language: A Complete Tutorial for DMN

FEEL - Friendly Enough Expression Language - is the standard expression language defined in the DMN 1.3 specification. Every condition in your decision tables, every output calculation, every hit policy expression is written in FEEL. If you use DMN for business rules - in Camunda, Priostack, or any OMG-compliant engine - you are already using FEEL, even if you did not know the name.

This tutorial covers everything you need to write effective FEEL expressions: the type system, arithmetic, string functions, date handling, list operations, and complete decision table examples you can drop into Priostack today.

What is FEEL?

FEEL was designed with a specific tension in mind: it must be expressive enough for a workflow engine to execute reliably, yet readable enough for a business analyst to write without a developer's help. The "Friendly Enough" in the name acknowledges this compromise.

Key characteristics of FEEL:

  • Statically typed - every value has a type: number, string, boolean, date, time, date-time, duration, list, context.
  • Explicit about null - null equals only null; x != null is true only when x has a value; <, <=, >, >= with null are false. In a gateway condition a variable that was never set is an error (the instance goes to incident), so initialise it before the gateway.
  • No side effects - expressions cannot modify state; they only compute values.
  • Readable range syntax - [18..65] means "between 18 and 65 inclusive".
  • Context variables - process variables are available by name, no prefix needed.

Basic Arithmetic

FEEL supports the standard arithmetic operators. There is one number type: no integer/float distinction. The table lists what the Priostack engine evaluates; it has no exponent operator (**) and no decimal() or modulo() function.

ExpressionResultNotes
1 + 23Addition
10 - 37Subtraction
4 * 2.510Multiplication
9 / 42.25Division
floor(3.7)3Round down
ceiling(3.2)4Round up
abs(-5)5Absolute value

Comparison and Range Expressions

FEEL's range syntax is one of its most powerful features for decision tables. Instead of writing age >= 18 and age <= 65, you write:

age in [18..65] // inclusive both ends age in ]17..65] // exclusive lower bound (>17, <=65) age in [18..65) // exclusive upper bound (>=18, <65) age >= 18 // standard comparison age != 0 // not equal

In decision table input cells you can omit the variable name:

[18..65] // matches if input variable is in this range < 18 // matches if input variable is less than 18 >= 1000 // matches if input variable >= 1000

String Functions

The Priostack engine evaluates these built-in string functions (substring after, string join, replace and trim are not implemented):

ExpressionResult
string length("hello")5
upper case("hello")"HELLO"
lower case("WORLD")"world"
substring("BPMN 2.0", 1, 4)"BPMN"
contains("Priostack", "stack")true
starts with("BPMN", "BPM")true
ends with("process.bpmn", ".bpmn")true
matches("abc123", "[a-z]+[0-9]+")true

String concatenation uses the + operator:

"Hello, " + customerName + "!" // "Hello, Alice!"

Date Operations

FEEL has first-class date, time, and duration types. This is critical for business rules involving deadlines, SLAs, and scheduling.

date("2026-05-06") // date literal time("14:30:00") // time literal date and time("2026-05-06T14:30:00") // date-time literal duration("P1Y2M3D") // period: 1 year, 2 months, 3 days duration("PT8H") // duration: 8 hours today() // current date now() // current date-time // Arithmetic on dates date("2026-05-06") + duration("P30D") // 2026-06-05 date("2026-06-05") - date("2026-05-06") // a duration of 30 days // Extracting parts date("2026-05-06").year // 2026 date("2026-05-06").month // 5 date("2026-05-06").day // 6 date("2026-05-06").weekday // 3 (Monday = 1 ... Sunday = 7) // Comparing dates in a gateway condition dueDate < today() // overdue check contractEnd - today() < duration("P30D") // expiring soon // In a decision table cell, write a unary test against the column instead: < date("2026-06-01") // a cell may not call today()

List Operations

FEEL lists are ordered sequences of any type. They are particularly useful for multi-value outputs and for checking membership:

["red", "green", "blue"] // list literal count([1,2,3,4]) // 4 sum([10, 20, 30]) // 60 min([5, 2, 8, 1]) // 1 max([5, 2, 8, 1]) // 8 // Membership (put a variable on the left of "in": the engine does not match a literal there) color in ("red", "green", "blue") // true when color = "red" list contains(["red","green","blue"], "red") // true score in [1..10] // true when score = 5 // Not implemented by the Priostack engine: mean, append, concatenate, // reverse, sort, flatten, distinct values and function literals. // Filtering (equivalent of WHERE clause) items[item > 100] // filter list by predicate some item in prices satisfies item > 500 // any item over 500? every item in scores satisfies item >= 60 // all passing?

Decision Table Examples

Let us see FEEL in action inside real DMN decision tables. In Priostack, you deploy a DMN table with POST /api/v1/models and call it from a business rule task or through the REST API.

Example 1: Loan Risk Rating

This decision table assigns a risk category based on credit score and loan amount:

Credit Score Loan Amount (€) Risk Category
>= 750< 50000"LOW"
[600..749][1..30000]"MEDIUM"
[500..599]-"HIGH"
< 500-"REJECT"

The output expression for a calculated interest rate could be:

// FEEL output expression based on riskCategory variable if riskCategory = "LOW" then 0.03 else if riskCategory = "MEDIUM" then 0.07 else if riskCategory = "HIGH" then 0.14 else null

Example 2: Discount Calculation

// Input: orderValue (number), customerTier ("GOLD" | "SILVER" | "STANDARD") // Output: discountPercent (number) if customerTier = "GOLD" and orderValue >= 1000 then 0.20 else if customerTier = "GOLD" then 0.15 else if customerTier = "SILVER" and orderValue >= 500 then 0.10 else if customerTier = "SILVER" then 0.05 else 0.0

Example 3: SLA Deadline Check

// Is a ticket overdue? ticketCreatedAt + duration("PT4H") < now() // Days remaining in contract (contractEndDate - today()).days // Is the request within business hours? (09:00-17:00 weekday) time(now()) >= time("09:00:00") and time(now()) <= time("17:00:00") and now().weekday <= 5 // Monday = 1 ... Friday = 5

Integration with Priostack DMN

Priostack evaluates FEEL expressions natively - no external rule engine, no scripting sandbox. A DMN decision table deploys on its own, through POST /api/v1/models (1 credit; the kind is detected from the XML), and is then called synchronously during process execution. Variables from the process context are automatically available as FEEL context entries.

To evaluate a deployed decision directly, call the Priostack REST API. The decision id is in the path, and the body is a flat object of inputs (each evaluation costs 1 credit):

POST /api/v1/decisions/loanRiskRating/evaluate X-API-Key: YOUR_API_KEY Content-Type: application/json { "creditScore": 680, "loanAmount": 25000 }

Response:

{ "result": { "riskCategory": "MEDIUM" } }

Output values come back as strings, and an unknown decision answers 422.

You can also call a deployed decision inline from BPMN with a business rule task carrying <zeebe:calledDecision decisionId="loanRiskRating"/>. The decision's outputs are written into the process under their own names (here riskCategory). No extra code needed.

For interactive FEEL expression testing and the full function reference, see the Priostack FEEL documentation.

Start using DMN and FEEL in your workflows today

Deploy your first decision table in minutes with a free Priostack account. No infrastructure required.

FEEL docs Quickstart guide

Frequently Asked Questions

What is FEEL in DMN?

FEEL stands for Friendly Enough Expression Language. It is the expression language defined in the DMN 1.3 specification, used to write input conditions and output values in decision tables. FEEL is designed to be readable by business analysts while being precise enough for execution engines.

Is FEEL case-sensitive?

Yes. FEEL is case-sensitive. Variable names, function names, and keywords must match exactly. true and false are lowercase boolean literals. Built-in functions like string length use lowercase with spaces.

What is the difference between FEEL and JUEL?

FEEL is the OMG-standard expression language for DMN decision tables. JUEL (Java Unified Expression Language) is a Java-specific EL used in older Camunda 7 versions. Priostack uses FEEL 1.3, which is portable and standards-based.

Can FEEL expressions access process variables?

Yes. When a DMN decision is evaluated from within a BPMN process, process variables are available as FEEL context variables by name. If your process has a variable customerAge, use it directly: customerAge >= 18.

How do I test FEEL expressions without deploying a full process?

There is no standalone FEEL evaluation endpoint. Deploy a small decision table with POST /api/v1/models and evaluate it with POST /api/v1/decisions/{decisionId}/evaluate (1 credit per call), or try the expression as a gateway condition in a short process. The FEEL reference lists what the engine evaluates.

Related: ArchiMate layers and enterprise modeling  ·  Two-layer BPMN architecture  ·  Full FEEL language reference