As a software engineer, you’ve been asked to help implement parts of a new BPMN or DMN model for your business. You feel quite comfortable with the APIs and code needed after reviewing the documentation, but you only took a quick look at the FEEL documentation. You’re a seasoned developer—it should be no problem to work with FEEL. You write your first FEEL expression to extract an element from a list, and suddenly the model doesn’t work, and you have errors saying your FEEL expression is invalid.
FEEL, or Friendly Enough Expression Language, generally lives up to its name: it is simple to get started with and understand, but powerful enough that you can write an expression for almost any data operation you need to perform. But for developers who are already familiar with other programming languages, there are some common pitfalls when getting started with FEEL.
What is FEEL exactly?
Before we look at those common pitfalls, let’s first define what, exactly, FEEL is and look at how it compares to other programming languages.
FEEL is part of the DMN specification from the Object Management Group (OMG). FEEL is not a fully featured programming language; rather, it is just for writing as expressions, as the name implies. You can chain those expressions together, so you can do some pretty complex data manipulation, but you can’t write a complete program using just FEEL.
While FEEL’s maintained spec is from OMG, there is no existing engine or runtime for it, so Camunda had to write its own! Camunda uses different FEEL interpreters, depending on what part of the application you are using.
Primarily, Camunda uses feel-scala. When Zeebe or the Connector Runtime evaluates a FEEL expression, it is using the feel-scala engine. When working with FEEL in an editor (for instance, inside Web Modeler), a linter is used to check for any syntax errors. In the few cases where Camunda needs to execute FEEL inside the browser (for instance, while creating Connector templates in Web Modeler), Camunda uses feelin, a JavaScript implementation of FEEL written by one of Camunda’s principal engineers, Nico Rehwaldt.
Types in FEEL vs. types in other languages
FEEL has several data types that you, as a developer, are most likely already familiar with. But FEEL uses some different terminology than other languages. (You can review all the data types and what Java types they map to here.) These are the key things you need to know:
- Null: Anything that doesn’t exist in FEEL is null. Referencing a variable that doesn’t exist in the current scope returns a null value.
- Numbers: Similar to JavaScript, there is one type to represent both integers and floating point numbers, signed or unsigned.
- Date, time, and duration: FEEL supports multiple different date and time types, including durations. It supports different formats for the date and time, and I recommend reviewing the documentation for that. There are two supported durations: days-time and years-months. Days-time duration can have a duration of days, hours, minutes, and seconds; years-months durations are, as the name suggests, durations in years and months. Both have a unique format for specifying the duration, which you can review here.
- Lists: Lists in FEEL are loosely typed arrays; they can contain any combination of other types.
- Contexts: Contexts in FEEL are, essentially, JSON objects. They are made up of key-value pairs, and the value can be any data type or expression.
Common pitfalls
FEEL has a different syntax and behavior in some cases; they make sense within the scope of what FEEL is trying to accomplish but might, at first, feel unintuitive for an experienced developer.
Spaces in function names
One interesting aspect of FEEL that stands out to many developers I’ve spoken with is that FEEL allows spaces in function names! Nearly every developer has needed to get the index of an element in an array. It might look like this: myArray.indexOf(value)
. In FEEL, it looks like this: index of(myArray, value)
. Perhaps you need to join a list into a single string. In another language, it may look like this: String.join(myArray, “, “)
. In FEEL, it looks like this: string join(myArray, “, “)
.
Sometimes it still feels strange to me to put spaces in function names, even after writing FEEL expressions for several years. However, it makes a lot of sense when you consider what FEEL is trying to accomplish. FEEL is meant to be accessible to non-developers, and using a more “natural language” style feels more intuitive and comfortable for those with no software development experience. After all, you shouldn’t have to be a seasoned programmer to build a DMN table!
List indexing and filtering
Lists (or arrays) in FEEL use one-based indexing. Arrays in most programming languages use zero-based indexing.
Take this expression, for example: = [“a”, “b”, “c”, “d”][1]
. If you wrote that in Java, you would expect the value b
to be returned, because Java uses zero-based indexing. With FEEL, you get the value a
. This one item is probably the most common issue developers encounter when they first start using FEEL.
Filtering a list also has a unique syntax in FEEL. Rather than pass the list and a condition to function, FEEL uses a condition inside the brackets where you typically specify the index. The filter exposes a variable named item
which you can use to build your condition:
={
“unfiltered”: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
“filtered”: unfiltered[even(item)]
}
(Try it in the FEEL Playground!)
Type coercion and concatenation
FEEL does not do any type coercion. In JavaScript, you can do something like ”1” + 1
and get 11
as the output. In FEEL, you will get null
instead. It doesn’t try to guess if you want to cast the string to a number and do addition, or 1 to a string and do concatenation. You must manually tell it what you want to happen: =”1” + string(1)
or =number(“1”) + 1
.
However, the number
function cannot coerce a non-numeric string to a number. This expression results in a value of null
: =number(‘string”) + 1
.
It is important to cast the data to the correct type before doing the operation. If this isn’t something your application can do, one best practice would be to do this in any result expressions or output mappings in the process to ensure that the data is already of the expected type.
Combining expressions
I compared FEEL’s context type to JSON objects. This is very true, they do resemble and act like JSON objects in many ways. However, FEEL contexts come with one added superpower: the expressions within the context are evaluated sequentially, so you can reference a previous key within the same context.
Let’s look at an example using some real world data.
I’m going to use a fun data set from the Marvel API (yes, a data set about comics)! This data set contains information about the character Spider-Man and related comics, stories, and artists. Don’t worry, you don’t have to use the API for this; you can look at the data set for these examples by clicking here.
In a separate blog post that introduced FEEL to Camunda users without development experience, I included this example:
{
"combinedListOfCreators": flatten(data.events.items.creators.items),
"onlyWriters": combinedListOfCreators[item.role = "writer"],
"onlyDistinctWriters": distinct values(onlyWriters),
"limit5Writers": sublist(onlyDistinctWriters, 1, 5),
"topCreators": string join(limit5Writers.name, ", ")
}
(Try it in the FEEL Playground!)
Notice how the onlyWriters
property references the result of the combinedListOfCreators
expression above it! This is a fun feature you can’t do in JSON. It allows you to build powerful expressions where needed.
Of course, not all those intermediate variables are necessary. You can combine it into one single expression:
= {
"topCreators": string join(
sublist(
distinct values(
flatten(data.events.items.creators.items)[item.role = "writer"]
), 1, 5).name, ", "
)
}
(Try it in the FEEL Playground!)
What’s next?
Congratulations, you made it to the end! You now know more about FEEL than you did just a few minutes ago. If you’re an experienced software developer, it may take a few moments to get comfortable working with FEEL. If you’re like me, after writing a few expressions, something will click and why Camunda uses FEEL within Zeebe will make sense.
This post doesn’t cover everything FEEL can do. So where to next?
- As always, the Camunda FEEL documentation is a great place to start. The docs cover all the available data types, expressions, and functions.
- Camunda Academy has a wonderful FEEL video series that provides examples in addition to describing the different features of FEEL.
- Find yourself having trouble getting your expression to work correctly? Join the Camunda community forum and ask your question there. Don’t forget to search, too!
There are some exciting new features coming to Camunda’s FEEL capabilities in the future, as well. In recent releases, we’ve improved the auto-complete in Camunda Modeler to better help with FEEL syntax. Keep an eye on future release announcements for other exciting new FEEL features.
Happy coding!
Start the discussion at forum.camunda.io