Zeebe supports FEEL v1.11 – the “Friendly Enough Expression Language” – to express dynamic behavior in your processes.

You can use FEEL expressions in a number of places in your BPMN models.

From the Zeebe documentation:

The following attributes of BPMN elements require an expression:

  • Sequence Flow on an Exclusive Gateway: condition
  • Message Catch Event / Receive Task: correlation key
  • Multi-Instance Activity: input collection, output element
  • Input/Output Variable Mappings: source

Additionally, the following attributes of BPMN elements can define an expression optionally instead of a static value:

  • Timer Catch Event: timer definition
  • Message Catch Event / Receive Task: message name
  • Service Task: job type, job retries
  • Call Activity: process id


The most obvious place that you use an expression in a model is a BPMN Gateway, to test some property of the workflow instance variables, and take a branch depending on its value.

gateway condition expressions

FEEL expressions start with an equals symbol =, to indicate that it is a FEEL expression that should be evaluated at runtime:

=time.hour >=0 and time.hour <=12

In this example, the FEEL expression examines the hour subkey of the time variable. It is a boolean expression using a Boolean AND operation. If the expression returns true, then the token will flow down this branch.

Note: be careful when constructing exclusive gateways to not specify overlapping conditions. If more than one branch evaluates to true, then the token will flow down one of the true branches – but it is unpredictable which it will take.

Safe Access

If you construct an expression that relies on a variable subkey, an incident will be raised if the variable does not exist.

You can test the existence of the variable first using the instance of operator:

=time instance of context and time.hour >=0 and time.hour <=12

The FEEL engine uses short-circuit evaluation – so if time is not a context, evaluation of the expression stops.

Zeebe 0.25 will introduce the function is defined for testing for the existence of a variable.

For a discussion on testing for null in versions of Zeebe prior to 0.25, see this GitHub issue.

Service Task

You can use a FEEL expression for the Type of a Service Task:

task type expression

=string(tenantId) + "-process-order"

In this example, the FEEL expression dynamically constructs the task type from the variable tenantId.

FEEL includes a number of built-in functions.

The string function will convert any variable value to a string value. Here we convert the tenantId to a string, then concatenate it with -process-order to get a unique task type.

If the tenantId variable in a workflow instance is set as 11011011, then the task type will be 11011011-process-order.

You could use this, for example, to segment workloads.

If you want to specialize worker behavior based on the value of a variable, you can do that in the worker.

However, if you want segmented workers for QoS, or with isolated access to distinct databases, you can use a FEEL expression to get this dynamic behavior from a single process model.


Exponential back-off retries is an open feature request for the core engine.

In the meantime, here is how you can use FEEL expressions to model a back-off retry for a task.

backoff retry

In the “Update Retries” worker, you need to update a variable in the workflow with the following structure:

  retry: {
    retries: $number,
    timeout: "$duration"

The worker code should set these values if they do not exist, and update them if they do, decrementing the retries and appropriately setting the timeout with the desired delay duration for the next retry (for example “P1D” for a one-day delay).

In the gateway branch leading to the “Back-off Timer”, set the Condition expression to =retry.retries > 0.

Set the “Back-off Timer” Timer Duration to =retry.timeout.

Note: if you use this pattern multiple times in the same process, you need to isolate the state – for example by using a unique key name for the retry key in each instance.

To use the same task type and handler code for the “Update Retries” worker, you could use custom headers to pass in the name of the key to use, and even to specify the back-off strategy to use.


The correlation key of a message catch or receive can be dynamically set.

The correlation key is the name of the variable whose value will be correlated with the message correlation key value.

You can dynamically set the correlation key based on a variable in the workflow instance.

This is similar to indirect addressing in assembly language programming.

If you set the correlation key for a message catch/receive event to =orderType + "Id", then with the following workflow variables, the subscription will be opened like this:

  orderType: "immediate",
  immediateId: 435

To publish a message to this subscription, you would set the correlation key in the publish message request to 435.


These are a few basic examples of using FEEL in Zeebe process models. In the next post, we’ll cover some more advanced uses.

What would you like to see covered in future articles? Let us know via feedback@zeebe.io. Have a question about Zeebe? Drop by the Slack channel or Zeebe User Forum.

Camunda Developer Community

Join Camunda’s global community of developers sharing code, advice, and meaningful experiences

  • Title slide that reads "Why Camunda 8"

    Why R-KOM Chose Camunda Platform 8

    In this blog series, we highlight the customers who have chosen to utilize Camunda Platform 8 and explore the challenges those companies are attempting to overcome using process orchestration. For the latest installment of Why Camunda 8, we spoke with R-KOM, a telecommunications company based in Regensburg, Germany. When R-KOM was founded in 1997, its shareholders pooled their telecommunications infrastructure, which had evolved over decades with utility networks for water, electricity, and gas. Initially, R-KOM’s services were limited to business and the public sector, but now it has developed further in line with demand. Over the years, the company’s high-performance infrastructure and a broad range of products have grown. Today, R-KOM has a number of city networks in Eastern Bavaria...

    Read more
  • Title slide that reads "Why Camunda 8"

    Why Gruner + Jahr Chose Camunda 8

    In this new blog series, we explore the reasons why customers are migrating to Camunda 8. For our first installment of Why Camunda 8, we talked to Gruner + Jahr, one of the largest premium magazine publishers in Europe. G+J includes such established (German) print and digital brands as STERN, GEO, BRIGITTE, ESSEN & TRINKEN, and SCHÖNER WOHNEN, as well as younger brands such as CHEFKOCH, BARBARA, BEEF, 11FREUNDE. In addition to the numerous print and digital media offerings, G+J offers products and licenses such as the SCHÖNER WOHNEN collection. The digital business contributes more than half of revenues and is exhibiting continued strong growth. Indeed, the company’s digital products lead the rankings in all publishing segments, from news through...

    Read more
  • Camunda Platform 8.1.0-alpha3 Released

    We’re excited to announce the release of Camunda Platform 8.1.0-alpha3. If you’d like to get started with Camunda Platform 8.1.0-alpha3 right away, you can sign up for a free trial now. Create Process Instance Starting at User-Defined Elements An often requested feature is now available as a preview with Camunda Platform 8.1.0-alpha3: create a process instance starting at user-defined elements. When creating a process instance through the CreateProcessInstance RPC, the process instance is started at the default none start event. For testing purposes, you may want to start at one (or multiple) of the other elements. This feature is now available through both the CreateProcessInstance RPC and the CreateProcessInstanceWithResult RPC. It is available for use in the Zeebe Java client...

    Read more

Ready to get started?

Still have questions?