Skip to main content

Back End Expressions

Introduction

Back end expressions are a tool to get or set variables during the execution of processes, cases, or decision tables. Back end expressions can also be used to implement custom logic or to delegate that logic to a Java service deployed on the server. Examples of using expressions usage are setting dynamic task names, evaluation of sequence flows after a gateway, or create a variable of a certain type. As the name implies, these expression run on the back end, during execution. As such they have no access to the frontend data at the time of evaluation.

In Flowable Design, expressions are allowed in fields marked with a lightning bolt: 500 bolt.

Flowable uses the Unified Expression Language (UEL) for resolving expressions. The documentation for UEL is a good reference for syntax and available operators. Each expression starts with ${ and ends with }.

There are two types of expressions:

  • value expressions provide a value. Supported values include Boolean, string, integer, floating-point number, and null. A typical value expression is ${variable.property} or ${bean.property}.

  • method expressions invoke a method with or without parameters. An example of method expression is ${bean.setPropertyValue('newValue')}. To distinguish between a value expression and a method expression without any parameters, use empty parentheses at the end of the method call. For example, ${variable.toString()}.

In theory, any Spring bean exposed to the application is available for use in backend expression, but not all classes can be serialized in a way which will allow for a proper expression evaluation.

info

Certain expressions are available in scripts (e.g. for use in a BPMN or CMMN script task). A list of expressions which can be used is available as part of the backend scripting documentation.

Keywords

The process and CMMN engines support context-dependent objects. Both engines resolve hierarchy objects like root and parent and can be used to access their local properties. root stands for the object on the top of the execution hierarchy, which can be a process instance or case instance. parent is a reference to the first process or case instance immediately above the current object in the hierarchy.

For process instances and case instances definition grants access to the definition and all its attributes.

In the context of BPMN execution, additional reserved keywords are execution and authenticatedUserId.

  • execution references to the current 'path of execution' or 'token'. It provides access to properties like name, businessKey, processDefinitionId, and parentExecution (technically, it is backed by the ExecutionEntity class).

  • task is a reference to the current task (technically, it is backed by the TaskEntity class).

  • authenticatedUserId is a string identifier of the currently authenticated user. If there is no authenticated user, the variable is not available.

The CMMN engine context supports task (see above), caseInstance, and planItemInstance.

  • caseInstance references to the current case instances. It provides access to properties like name, state, businessKey, caseDefinitionId, and parentId (technically, it is backed by the CaseInstanceEntity class).

  • planItemInstance references the plan item instance associated with the case.

Expressions Overview

Process Instance Properties

The following properties are available on a process instance, for example when using root or parent.

Example usage: ${root.businessKey}.

PropertyDescription
businessKeyThe business key of the process instance, if set.
businessStatusThe business status of the process instance, if set.
callbackIdIf the process instance is started through a process task in a CMMN case instance, this value will reference the id of the associated plan item instance.
descriptionThe description of the process instance, if set.
definitionThe process definition. See below for more details.
idThe unique technical identifier of the process instance.
nameThe name of the process instance, if set.
startTimeThe time (as a java.util.Date) this process instance was started.
startUserIdThe id of the user that started this process instance.
tenantIdThe tenant identifier of this process instance.

Case Instance Properties

The following properties are available on a process instance, for example when using root or parent.

Example usage: ${parent.businessStatus}.

PropertyDescription
businessKeyThe business key of the case instance, if set.
businessStatusThe business status of the case instance, if set.
callbackIdIf the case instance is started through a case task in a CMMN case instance, this value will reference the id of the associated plan item instance.
descriptionThe description of the case instance, if set.
definitionThe case definition. See below for more details.
idThe unique technical identifier of the case instance.
nameThe name of the case instance, if set.
startTimeThe time (as a java.util.Date) the case instance was started.
startUserIdThe id of the user that started the case instance.
tenantIdThe tenant identifier of the case instance.

Definition Properties

PropertyDescription
idThe unique technical identifier of the definition.
categoryThe category of the definition, if set in the related model.
deploymentIdA reference to the deployment related to this definition. See the documentation on deployments to learn more what this means.
descriptionThe description of the definition, if configured in the model.
keyThe key of the definition, which must be unique for all similar model types. For a given key, multiple versions are typically deployed in a runtime system.
nameThe name of the definition, which matches the name of the BPMN or CMMN model.
tenantIdThe identifier of the tenant in which this definition was deployed.
versionThe version of the definition. Each time a model is published, a new definition for the same key is created with version equal to the last version + 1.

Variable Functions

Variable manipulation is an important use case of expressions.

Setting variables can be done through the runtimeService:

  • ${runtimeService.setVariable(processInstanceId, variableName, variableValue)}
  • ${cmmnRuntimeService.setVariable(caseInstanceId, variableName, variableValue)}

Or on a keyword object (depending on the context):

  • ${execution.setVariable(varName, varValue)} sets the value varValue to variable varName of the current process instance.
  • ${caseInstance.setVariable(varName, varValue)} sets the value varValue to variable varName of the current case instance.
  • ${planItemInstance.setVariable(varName, varValue)} sets the value varValue to variable varName of the current case instance.
  • ${task.setVariable(varName, varValue)} sets the value varValue to variable varName of the current process/case instance.

Following functions are available for handling variables, both applicable to BPMN and CMMN context:

FunctionDescription
${var:get(varName)}Retrieves the value of a variable. The main difference with writing the variable name directly in the expression is that using this function won’t throw an exception when the variable doesn’t exist. For example ${myVariable == "hello"} would throw an exception if myVariable doesn’t exist, but ${var:get(myVariable) == 'hello'} won't.
${var:getOrDefault(varName, defaultValue)}variables:getOrDefault(varName, defaultValue).
${var:exists(varName)}variables:exists(varName).
${var:isEmpty(varName)}Checks if the variable value is not empty. Depending on the variable type, the behavior is the following. For String variables, the variable is deemed empty if it’s the empty string. For java.util.Collection variables, true is returned if the collection has no elements. For ArrayNode variables, true is returned if there are no elements. In case the variable is null, true is always returned.
${var:isNotEmpty(varName)}The reverse operation of isEmpty.
${var:equals(varName, value)}Checks if a variable is equal to a given value. This is a shorthand function for an expression that would otherwise be written as ${execution.getVariable("varName") != null && execution.getVariable("varName") == value}. If the variable value is null, false is returned (unless compared to null).
${var:notEquals(varName, value) }The reverse operation of equals.
${var:contains(varName, value1, value2, …​)}Checks if all values provided are contained within a variable. Depending on the variable type, the behavior is the following. For String variables, the passed values are used as substrings that need to be part of the variable. For java.util.Collection variables, all the passed values need to be an element of the collection (regular contains semantics). For ArrayNode variables: supports checking if the arraynode contains a JsonNode for the types that are supported as variable type. When the variable value is null, false is returned in all cases. When the variable value is not null, and the instance type is not one of the types above, false will be returned.
${var:containsAny(varName, value1, value2, …​)}Similar to the contains function, but true will be returned if any (and not all) the passed values is contained in the variable.
${var:base64(varName}Converts a Binary or String variable to a Base64 String.
${var:lowerThan(varName, value)}shorthand for ${execution.getVariable("varName") != null && execution.getVariable("varName") < value}. Alias = lt
${var:lowerThanOrEquals(varName, value)}Similar, but now for < =. Alias = lte
${variables:greaterThan(varName, value)}Similar, but now for >. Alias = gr
${variables:greaterThanOrEquals(varName, value)}Similar, but now for > =. Alias = gte

Process Assignment Functions

The following functions around assignment are available in BPMN process execution context:

FunctionDescription
${bpmn:setAssignee(processInstanceId, userId)}set the assignee of the process with the given processInstanceId it to the given userId
${bpmn:getAssignee()} returns assignee ofcurrent process
${bpmn:getAssignee(processInstanceId)}returns the assignee of the process with the given processInstanceId
${bpmn:removeAssignee()}removes the assignee of the current process
${bpmn:removeAssignee(processInstanceId)}removes the assignee of the process with the given processInstanceId
${bpmn:setOwner(processInstanceId, userId)}set the owner of the process with the given processInstanceId to the given userId
${bpmn:getOwner()}returns the owner of current process
${bpmn:getOwner(processInstanceId)}returns the owner of the process with the given processInstanceId
${bpmn:removeOwner()}removes the owner of the current process
${bpmn:removeOwner(processInstanceId)}removes the owner of the process with the given processInstanceId
${bpmn:addParticipantUser(processInstanceId, userId)}add the user with the given userId as participant to the process with the given processInstanceId
${bpmn:addParticipantUsers(processInstanceId, userIds)}add the users with the given userIds as participant to the process with the given processInstanceId. The userIds can be passed in as ArrayNode, Iterable, or comma separated list of userIds.
${bpmn:removeParticipantUser(processInstanceId, userId)}remove the user with the given userId as participant of the process with the given processInstanceId
${bpmn:removeParticipantUsers(processInstanceId, userIds)}remove the users with the given userIds as participants from the process with the given processInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${bpmn:addCandidateUser(processInstanceId, userId)}add the user with the given userId as candidate to the process with the given processInstanceId
${bpmn:addCandidateUsers(processInstanceId, userIds)}add the users with the given userIds as candidates to the process with the given processInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${bpmn:removeCandidateUser(processInstanceId, userId)}remove the user with the given userId as candidate of the process with the given processInstanceId
${bpmn:removeCandidateUsers(processInstanceId, userIds)}remove the users with the given userIds as candidates from the process with the given processInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${bpmn:addCandidateGroup(processInstanceId, groupId)}add the group with the given groupId as candidate group to the process with the given processInstanceId
${bpmn:addCandidateGroups(processInstanceId, groupIds)}add the groups with the given groupIds as candidate groups to the process with the given processInstanceId. The groupIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${bpmn:removeCandidateGroup(processInstanceId, groupId)}remove the group with the given userId as candidate group of the process with the given processInstanceId
${bpmn:removeCandidateGroups(processInstanceId, groupIds)}remove the groups with the given groupIds as candidate groups from the process with the given processInstanceId. The groupIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.

Case Assignment Functions

The following functions around assignment are available in CMMN case execution context:

FunctionDescription
${cmmn:setAssignee(caseInstanceId, userId)}set the assignee of the case with the given case it to the given userId
${cmmn:getAssignee()}returns the assignee of the current case
${cmmn:getAssignee(caseInstanceId)}returns the assignee of the case with the given caseInstanceId
${cmmn:removeAssignee()}removes the assignee of the current case
${cmmn:removeAssignee(caseInstanceId)}removes the assignee of the case with the given caseInstanceId
${cmmn:setOwner(caseInstanceId, userId)}set the owner of the case with the given caseInstanceId to the given userId
${cmmn:getOwner()} returns the ownerof the current case
${cmmn:getOwner(caseInstanceId)} returns theowner of the case with the given caseInstanceId
${cmmn:removeOwner()} removes the ownerof the current case
${cmmn:removeOwner(caseInstanceId)} removes theowner of the case with the given caseInstanceId
${cmmn:addParticipantUser(caseInstanceId, userId)}add the user with the given userId as participant to the case with the given caseInstanceId
${cmmn:addParticipantUsers(caseInstanceId, userIds)}add the users with the given userIds as participant to the case with the given caseInstanceId. The userIds can be passed in as ArrayNode, Iterable, or comma separated list of userIds.
${cmmn:removeParticipantUser(caseInstanceId, userId)}remove the user with the given userId as participant of the case with the given caseInstanceId
${cmmn:removeParticipantUsers(caseInstanceId, userIds)}remove the users with the given userIds as participants from the case with the given caseInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${cmmn:addCandidateUser(caseInstanceId, userId)}add the user with the given userId as candidate to the case with the given caseInstanceId
${cmmn:addCandidateUsers(caseInstanceId, userIds)}add the users with the given userIds as candidates to the case with the given caseInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${cmmn:removeCandidateUser(caseInstanceId, userId)}remove the user with the given userId as candidate of the case with the given caseInstanceId
${cmmn:removeCandidateUsers(caseInstanceId, userIds)}remove the users with the given userIds as candidates from the case with the given caseInstanceId. The userIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${cmmn:addCandidateGroup(caseInstanceId, groupId)}add the group with the given groupId as candidate group to the case with the given caseInstanceId
${cmmn:addCandidateGroups(caseInstanceId, groupIds)}add the groups with the given groupIds as candidate groups to the case with the given caseInstanceId. The groupIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.
${cmmn:removeCandidateGroup(caseInstanceId, groupId)}remove the group with the given userId as candidate group of the case with the given caseInstanceId
${cmmn:removeCandidateGroups(caseInstanceId, groupIds)}remove the groups with the given groupIds as candidate groups from the case with the given caseInstanceId. The groupIds can be passed in as ArrayNode of TextNode, Iterable of String, or comma separated String of userIds.

CMMN Functions

The following functions are available in CMMN context:

FunctionDescription
${cmmn:copyLocalVariable(planItemInstance, localVariableName, index, variableName)}Copies an planItemInstance-local variable to the case instance.
${cmmn:getTask(taskId)}Returns the task (runtime or historical) with the provided id.
${cmmn:isPlanItemCompleted(planItemInstance)}This function evaluates a plan item to be completed, which is most likely used on a plan item with a repetition rule to check, whether it has alreday been completed before.
${cmmn:isStageCompletable()}Returns whether the current case stage is completable (depends on the configuration of the stage and its elements, see 'auto complete' and others).
${cmmn:replaceVariableInList(planItemInstance, variableName, index, listVariableName)}Changes the entry on the provided index of a list with the given listVariableName with the value of the variable referenced by variableName.
${cmmn:setBusinessStatus(caseInstanceId, businessStatus)}v3.16.0+ Changes the business status property of the case instance matching the provided caseInstanceId.
${cmmn:triggerCaseEvaluation(<parameter>)}Triggers the evaluation of the case instance sentries of the case instance. The parameter can be a planItemInstance, a caseInstance or the id of a case instance.

BPMN Functions

The following functions are available in BPMN context:

FunctionDescription
${bpmn:copyLocalVariable(localVariableName, index, variableName)}Copies an execution-local variable to the process instance.
${bpmn:copyLocalVariableToParent(localVariableName, index, variableName)}Copies an execution-local variable to its parent execution.
${bpmn:getTask(taskId)}Returns the task (runtime or historical) with the provided id.
${bpmn:replaceVariableInList(variableName, index, listVariableName)}Changes the entry on the provided index of a list with the given listVariableName with the value of the variable referenced by variableName.
${bpmn:setBusinessStatus(processInstanceId, businessStatus)}v3.16.0+ Changes the business status property of the process instance matching the provided processInstanceId.
${bpmn:triggerCaseEvaluation()}Triggers the evaluation of case instance sentries of the case instance that is the parent of the process instance.

String Utilities

String manipulation methods are a classic use case for expressions. The following methods are available:

ExpressionDescription
${flwStringUtils.carriageReturn()}Returns the carriage return character.
${flwStringUtils.capitalize(text)}Capitalizes a text, i.e. sets the first letter to uppercase. Supported values are String, a Json TextNode or null.
${flwStringUtils.contains(text, otherText)}Checks if a strings contains another string. Supported values are String, a Json TextNode or null.
${flwStringUtils.containsIgnoreCase(text, otherText)}Checks if a strings contains another string ignoring the case. Supported values are String, a Json TextNode or null.
${flwStringUtils.equals(text, otherText)}Checks if two strings are the equal. Supported values are String, a Json TextNode or null.
${flwStringUtils.equalsIgnoreCase(text, otherText)}Checks if two strings are the equal, ignoring the case. Supported values are String, a Json TextNode or null.
${flwStringUtils.escapeHtml(text)}Escapes the characters in an object using HTML entities. Supported values are String, a Json TextNode or null.
${flwStringUtils.hasText(text)}Checks whether a string contains text (is not null or empty). Supported values are String, a Json TextNode or null.
${flwStringUtils.join(collection, String delimiter)}Concatenates all entries of a list or collection to a single string using a given delimiter. Supported values are String, a Json TextNode or null.
${flwStringUtils.matches(text, regularExpression)}Checks whether a text matches a regular expression. Supported values are String, a Json TextNode or null.
${flwStringUtils.newline()}Returns a new line linefeed character.
${flwStringUtils.substring(text, from, to)}Returns a substring within a provided character range. Index is 0 based, from is inclusive, to is exclusive. Supported values are String, a Json TextNode or null.
${flwStringUtils.substringFrom(text, from)}Returns the text starting at a given position (index is 0 based). Supported values are String, a Json TextNode or null.
${flwStringUtils.split(text, delimiter)}Splits a text into a collection with a given delimiter. The delimiter can be a single character, e.g. a semicolon or a regular expression. Supported values are String, a Json TextNode or null.
${flwStringUtils.toLowerCase(text)}Turns all characters of the string to lowercase.
${flwStringUtils.toUpperCase(text)}Turns all characters of the string to uppercase.
${flwStringUtils.trimWhitespace(text)}Removes all leading and trailing whitespaces from a text. Supported values are String, a Json TextNode or null.
${flwStringUtils.unescapeHtml(text)}Unescapes a string containing entity escapes to a string containing the actual Unicode characters corresponding to the escapes. Supported values are String, a Json TextNode or null.

Date and Time Utilities

ExpressionDescription
${flwTimeUtils.now()}Returns the current date and time in UTC.
${flwTimeUtils.currentDate()}Returns the current date at 00:00 AM UTC as an Instant.
${flwTimeUtils.currentLocalDate()}Returns the current date in the system time zone (which is UTC) as a LocalDate instance.
${flwTimeUtils.currentLocalDateTime()}Returns the current date and time in the system time zone (which is UTC) as a LocalDateTime instance.
${flwTimeUtils.instantFromTimestamp(long timestamp)}Returns an Instant from the number of seconds that have passed since the first of January 1970 (Unix Timestamp).
${flwTimeUtils.dateFromTimestamp(long timestamp)}Returns an Date from the number of seconds that have passed since the first of January 1970 (Unix Timestamp).
${flwTimeUtils.parseInstant(Date date)}Returns an Instant out of a Date }
${flwTimeUtils.parseInstant(String instantIsoString)}Parses an ISO8601 formatted string into an Instant.
${flwTimeUtils.parseInstant(Object value, String pattern)}Parses an object into an Instant using the provided pattern. Supported inputs are String, Json TextNode or null.
${flwTimeUtils.parseLocalDate(Object value, String pattern)}Parses an object into a LocalDate using the provided pattern. Supported inputs are String, Json TextNode or null.
${flwTimeUtils.parseLocalDateTime(Object value, String pattern)}Parses an object into a LocalDateTime using the provided pattern. Supported inputs are String, Json TextNode or null.
${flwTimeUtils.asInstant(Object value)}Converts a value to an Instant with UTC time zone. Supported values are: Date, Instant (which will be returned directly), LocalDate, LocalDateTime, an ISO8601 formatted String or null.
${flwTimeUtils.asInstant(Object value, String timeZoneId)}Converts a value to an Instant in the given time zone. Supported values are: Date, Instant (which will be returned directly), LocalDate, LocalDateTime, an ISO8601 formatted String or null.
${flwTimeUtils.asLocalDate(Object value)}Converts a value to a LocalDate with UTC time zone. Supported values are: Date, Instant, LocalDate (which will be returned directly), LocalDateTime, an ISO8601 formatted String or null.
${flwTimeUtils.asLocalDate(Object value, String timeZoneId)}Converts a value to an LocalDate in the given time zone. Supported values are: Date, Instant, LocalDate (which will be returned directly), LocalDateTime, an ISO8601 formatted String or null.
${flwTimeUtils.asLocalDateTime(Object value)}Converts a value to a LocalDateTime with UTC time zone. Supported values are: Date, Instant, LocalDate, LocalDateTime (which will be returned directly), an ISO8601 formatted String or null.
${flwTimeUtils.asLocalDateTime(Object value, String timeZoneId)}Converts a value to an LocalDateTime in the given time zone. Supported values are: Date, Instant, LocalDate, LocalDateTime (which will be returned directly), an ISO8601 formatted String or null.
${flwTimeUtils.asDate(Object value)}Converts a value to a Date in the UTC time zone. Supported values are Instant, LocalDate, LocalDateTime, an ISO8601 formatted String or null.
${flwTimeUtils.atTime(Object value, int hours, int minutes, int seconds)}Sets the time of the value to the specified hours, minutes and seconds in the UTC time zone. Supported values are Date, Instant, LocalDateTime or an ISO8601 formatted String. The returned value will be the same type as the input type, for a string, it will either be an Instant or a LocalDateTime depending on the provided format of the string.
${flwTimeUtils.atTimeWithTimeZone(Object value, int hours, int minutes, int seconds, String timeZoneId)}Sets the time of the value to the specified hours, minutes and seconds in the given time zone. Supported values are Date, Instant, LocalDateTime or an ISO8601 formatted String. The returned value will be the same type as the input type, for a string, it will either be an Instant or a LocalDateTime depending on the provided format of the string.
${flwTimeUtils.atTimeZone(Object value, String timeZoneId)}Returns an Instant at a specified time zone. Supported values are Date or Instant. Only use this one for display purposes, never store a date in something else than UTC.
${flwTimeUtils.getAvailableTimeZoneIds()}Returns a list of available time zone ids. A more or less complete list can also be found here
${flwTimeUtils.getField(Object value, String chronoFieldString)}Obtains a 'slice of time' by specifying a ChronoField as a String. The chrono field can be specified either as the display name or the enum name. Supported values are Date, Instant, LocalDate, LocalDateTimeor an ISO8601 formatted String. A list of all chrono fields can be found here. Example: flw.time.getField(flw.time.now(), 'DAY_OF_WEEK'), or flw.time.getField(flw.time.now(), 'AlignedWeekOfYear')}
${flwTimeUtils.isWeekend(Object value)}Determines whether the given value represents a weekend. Supported values are Date, Instant, LocalDate, LocalDateTime or a ISO8601 formatted String.
${flwTimeUtils.fullDateTimeInstant(int year, int month, int day, int hour, int minute, int second)}Creates an Instant with the given values in UTC time zone.
${flwTimeUtils.fullDateTimeDate(int year, int month, int day, int hour, int minute, int second)}Creates a Date with the given values.
${flwTimeUtils.plusSeconds(Object value, long seconds)}Adds seconds to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusMinutes(Object value, long minutes)}Adds minutes to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusHours(Object value, long hours)}Adds hours to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusDays(Object value, long days)}Adds days to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusWeeks(Object value, long weeks)}Adds weeks to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusMonths(Object value, long months)}Adds months to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusYears(Object value, long years)}Adds years to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.plusDuration(Object value, String iso8601Duration)}Adds an ISO8601 encoded duration to a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. Examples: flw.time.plusDuration(flw.time.now(), 'P1Y') (adds one year to now), flw.time.plusDuration(flw.time.now(), 'P14D') (adds 14 days to now), flw.time.plusDuration(flw.time..now(), 'PT30M10S') (adds 30 minutes and 10 seconds to now).
${flwTimeUtils.minusSeconds(Object value, long seconds)}Subtracts seconds from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusMinutes(Object value, long minutes)}Subtracts minutes from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusHours(Object value, long hours)}Subtracts hours from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusDays(Object value, long days)}Subtracts days from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusWeeks(Object value, long weeks)}Subtracts weeks from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusMonths(Object value, long months)}Subtracts months from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusYears(Object value, long years)}Subtracts years from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minusDuration(Object value, String iso8601Duration)}Subtracts an ISO8601 encoded duration from a given value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. Examples: flw.time.minusDuration(flw.time.now(), 'P1Y') (subtracts one year from now), flw.time.minusDuration(flw.time.now(), 'P14D') (subtracts 14 days from now), flw.time.minusDuration(flw.time..now(), 'PT30M10S') (subtracts 30 minutes and 10 seconds from now).
${flwTimeUtils.secondsOfDuration(String iso8601Duration)}Returns the number of seconds in a ISO duration string, e.g. PT60M returns 3600.
${flwTimeUtils.isBefore(Object firstValue, Object secondValue)}Checks if the first value is before the second value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The values don't need to be the same type, they will be converted automatically, if needed.
${flwTimeUtils.isBeforeOrEqual(Object firstValue, Object secondValue)}Checks if the first value is before or equals to the second value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The values don't need to be the same type, they will be converted automatically, if needed.
${flwTimeUtils.isAfter(Object firstValue, Object secondValue)}Checks if the first value is after the second value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The values don't need to be the same type, they will be converted automatically, if needed.
${flwTimeUtils.isAfterOrEqual(Object firstValue, Object secondValue)}Checks if the first value is after or equal to the second value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The values don't need to be the same type, they will be converted automatically, if needed.
${flwTimeUtils.areEqual(Object firstValue, Object secondValue)}Checks if the first value is equal to the second value. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The values don't need to be the same type, they will be converted automatically, if needed.
${flwTimeUtils.isBeforeTime(Object value, String timeZoneId, int hours, int minutes, int seconds)}Checks if the given value is before a certain time in a given time zone. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.isAfterTime(Object value, String timeZoneId, int hours, int minutes, int seconds)}Checks if the given value is after a certain time in a given time zone. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.getFieldFromDurationBetweenDates(Object firstValue, Object secondValue, String chronoUnitString)}Returns the duration between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String. The following units are supported: Nanos, Micros, Millis, Seconds, Hours, HalfDays, Days, Weeks, Months, Years, Decades, Centuries, Millenia. Please note that the number will always be a long, i.e. the number will always be rounded.
${flwTimeUtils.secondsBetween(Object firstValue, Object secondValue)}Return the number of seconds between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.minutesBetween(Object firstValue, Object secondValue)}Return the number of minutes between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.hoursBetween(Object firstValue, Object secondValue)}Return the number of hours between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.daysBetween(Object firstValue, Object secondValue)}Return the number of days between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.weeksBetween(Object firstValue, Object secondValue)}Return the number of weeks between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.monthsBetween(Object firstValue, Object secondValue)}Return the number of month between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.yearsBetween(Object firstValue, Object secondValue)}Return the number of years between two values. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.
${flwTimeUtils.getTimeZoneOffset(Object value, String timeZoneId)}Calculates the number of seconds a specific point in time at a specified time zone is set off from UTC. Supported values are Date, Instant, LocalDate, LocalDateTime or an ISO8601 formatted String.

JSON Utilities

The following functions are available when working with JSON objects in expressions. They work in BPMN and CMMN context.

ExpressionDescription
${json:object()} returns an empty json object.
${json:array()} returns an empty json array.
${json:arrayWithSize(size)} returns a json array with a given size.
${json:addToArray(arrayNode, object)} adds a given object to a given array node.

Formatting Utilities

The following methods are available when needing to format values:

ExpressionDescription
${flwFormatUtils.formatString(text, substitutes)}Formats a string according to the Java formatter specification, see here or here.
${flwFormatUtils.formatDate(value, dateFormat)}Formats the value to a string with the given format. Supported values are Date, Instant, LocalDate, LocaDateTime or an ISO8601 formatted string.
${flwFormatUtils.formatDecimal(pattern, decimal)}Formats a string according to the Java formatter specification with a decimal formatter in the default locale. See
${flwFormatUtils.formatStringWithLocale(languageTag, text, substitutes)}Formats a string according to the Java formatter specification. The string is formatted according to the format specified in the locale of the provided language tag. See See here or here
${flwFormatUtils.formatCurrencyWithLocale(currencyCode, amount, languageTag)}Formats a currency amount according to the format specified in the locale of the provided language tag.

Example: ${flwFormatUtils.formatDate(flwTimeUtils.now(), 'dd.MM.yyyy')} formats today's date as dd.MM.yyyy

Locale Utilities

ExpressionDescription
${flwLocaleUtils.getLocaleForLanguageTag(languageTag)}Returns the locale with the given language tag, e.g. 'ch-DE'.
${flwLocaleUtils.getAvailableLocales()}Returns a list of available locales.
${flwLocaleUtils.getDefaultLocale()}Returns the system default locale.
${flwLocaleUtils.getAllCountryCodes()}Returns a list of all 2-letter ISO country codes.
${flwLocaleUtils.getAllLanguageCodes()}Returns a list of all ISO language codes, e.g. "de" (NOT "de-CH").
${flwLocaleUtils.getLanguageDisplayName(languageIsoCode, displayLanguageTag)}Returns a single language name in a certain language, e.g. "German" or "Spanish".
${flwLocaleUtils.getCountryDisplayName(languageTag, displayLanguageTag)}Returns a single country name in a certain language, e.g. "Switzerland" or "Germany".
${flwLocaleUtils.getAllLanguageDisplayNames(displayLanguageTag)}Returns a list of all language names in a certain language.
${flwLocaleUtils.getAllCountryDisplayNames(displayLanguageTag)}Returns a list of all country names in a certain language.

Mathematical Utilities

The following methods are available when needing to do mathematical operations:

ExpressionDescription
${flwMathUtils.abs(number)}Returns the absolute value of a number.
${flwMathUtils.average(numbers}Calculates the average of a list of numbers.
${flwMathUtils.ceil(number)}Returns the next higher integer of a provided number.
${flwMathUtils.floor(number)}Returns the next lower integer of a provided number.
${flwMathUtils.median(numbers)}Returns the median of a list of numbers.
${flwMathUtils.min(numbers)}Returns the lowest number from a list of numbers.
${flwMathUtils.max(numbers)}Returns the highest number from a list of numbers.
${flwMathUtils.parseDouble(string)}Converts a string into a double value.
${flwMathUtils.parseInt(string)}Converts a string into an integer value.
${flwMathUtils.round(number)}Rounds a number to an integer value.
${flwMathUtils.round(number, scale)}Round a number to a maximum of decimal places using RoundingMode#HALF_UP.
${flwMathUtils.sum(numbers)}Calculates the sum of a list of numbers.

User functions

The following methods can be used for retrieving user information:

ExpressionDescription
${findUser(userId)}Returns the user representation corresponding to passed id.
${findGroupMemberUserIds('GROUP_A, GROUP_B') v3.14.0+Returns a string Collection of userIds of the members of the given groups. Note: string Collection String should only be stored transiently, if you want to persist it as a variable, you'll have to convert it to JSON.
${findGroupMemberEmails('GROUP_A, GROUP_B') v3.14.0+Returns a string Collection of emails of the members of the given groups. Users without emails are ignored.
${isUserInAllGroups(userId, groupKeys)}Returns true if the given userId is in the provided groups with the given keys.
${isUserInAnyGroup(userId, groupKeys)}Returns true if the given userId is in the provided groups with the given keys.
${isUserInNoGroup(userId, groupKeys)}Returns true if the given userId is in none of the provided groups with the given keys.
${userInfo:findUserInfo(property, findUser(authenticatedUserId))}Returns a given user infoproperty of the current user.
${userInfo:findBooleanUserInfo(property, findUser(authenticatedUserId))}Returns a given boolean user info property of the current user.

Task Service

The task service can be used in expressions to programmatically interact with tasks

ExpressionDescription
${taskService.claim(taskId, userId)}Claims the task with the given id by the user with the provided user id.
${taskService.unclaim(taskId)}Unclaims the task after it was claimed.
${taskService.complete(taskId, userId, variables)}Completes the given task. The userId and variables (Map of String to Objects) are optional.
${taskService.setDueDate(taskId, userId)}Sets a due date for the task.
${taskService.setPriority(taskId, userId)}Sets a priority for the task.
${taskService.setAssignee(taskId, userId)}Sets the given userId as the assignee for the task.
${taskService.setOwner(taskId, userId)}Sets the given userId as the owner for the task.
${taskService.addCandidateUser(taskId, userId)}Adds the given userId as a candidate user for the task.
${taskService.addCandidateGroup(taskId, userId)}Adds the given groupId as a candidate group for the task.
${taskService.addUserIdentityLink(taskId, userId, identityLink)}Adds a user identity link to the given task.
${taskService.addGroupIdentityLink(taskId, groupId, identityLink)}Adds a group identity link to the given task.

Sequence Models

When working with sequence models, there is often a need to generate these numbers in e.g. names of activities of BPMN or CMMN instances. The following functions are available to do that:

ExpressionDescription
${seq:nextNumber(<parameter>, sequenceDefinitionKey)Returns the next number of the sequence. The parameter should be a runtime object that has access to the current variables, such as a process instance, a case instance, an execution, a task, a plan item instance, etc.. The sequenceDefinitionKey should match the key set in the sequence model.
${seq:next(<parameter>, sequenceDefinitionKey)Similar as the previous one, but also takes into account the configuration settings for formatting that have been set in the model.

Configuration Service

The configuration service allows to access configuration properties (for example those which are set in application.properties file or similar.) In expressions, this service is useful as it allows changing behavior based on environment specific properties (e.g. DEV, TEST, PROD env).

ExpressionDescription
${propertyConfigurationService.getProperty(key, defaultValue)}returns the string value of a configuration property.
${propertyConfigurationService.getBooleanProperty(key, defaultValue)}returns the boolean value of a configuration property.
${propertyConfigurationService.getIntegerProperty(key, defaultValue)}returns the integer value of a configuration property.

Auth Tokens

This advanced expression is useful when working with external services and OAuth. Obviously storing the credentials hard-coded in expressions is a no-go. This expreesion will help with that:

  • ${flwAuthTokenUtils.getAccessToken('externalServiceId')} (note: resolves an access token for the given external service id, when a Spring Security OAuth2 Client configuration is available for the given externalServiceId)

Collection Utils

The following options are for working with collections or list of ContentItem.

ExpressionDescription
${flwCollectionUtils.emptyNode()}Create an empty JSON object node.
${flwCollectionUtils.emptyList()}Create an empty Java List. The result should not be directly stored as a none transient variable.
${flwCollectionUtils.emptyMap()}Create an empty Java Map. The result should not be directly stored as a none transient variable.
${flwCollectionUtils.distinct(list)}Remove duplicate elements from a Java list. The result should only be stored as a none transient variable for ContentItems.
${flwCollectionUtils.listWithElements(itemOne, itemTwo, ...)}Creates a Java list with multiple elements. The result should only be stored as a none transient variable for ContentItem's.
${flwCollectionUtils.mergeCollections(collection1, collection2, ...)}Merges multiple Java collections to a single list. The result should only be stored as a none transient variable for ContentItem's.
caution

When using List or Map from Java, they will be stored as a serializable in the Flowable databases. This means, that they will be stored in the byte array table, and it might get inefficient for large amount of data. The only exception from this is for List of ContentItem.

Customization

Use Case

As an example, assume a process instance that is dependent on a department within a company. The user task assignment is then dependent on the department in which process instance is executed. Expressions are a natural way of determining assignees.

Assume there is a reference to the department as a process instance variable. Then given the department id, it is used to find members of the department and use that information to determine assignees for the given process.

Implementation Using Beans

The most straight forward way to extend the functionality available in expressions is to expose a Spring bean with a method. By default, expressions can contain Spring beans references.

The bean in the Spring configuration is:

@Bean
public DepartmentResolver departmentResolver() {
return new DepartmentResolver();
}

And for illustrative purposes this is a simple resolver:

public class DepartmentResolver {

public String getAssignee(ExecutionEntity execution) {
String departmentId = execution.getVariable("departmentId", String.class);
return departmentId + ".assignee";
}
}

The final step is to use the bean in the process definition:

<process id="oneTaskProcess">

<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="theTask"/>

<userTask id="theTask" name="My Task" flowable:assignee="${departmentResolver.getAssignee(execution)}"/>
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />

<endEvent id="theEnd"/>

</process>

A JUnit test verifies that user task assignee is the assignee from the DepartmentResolver:

@Test
@Deployment(resources = "AssigneeExpressionBeanTest.oneTaskProcessWithBeanAssignment.bpmn20.xml")
public void springBeanAssignUserTask() {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("departmentId", "testDepartmentId"));
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();

assertThat(task.getAssignee()).isEqualTo("testDepartmentId.assignee");
}

For a CMMN example see SpringCustomBeanTest and associated files and f resources.

Implementation Using Expression Resolvers

Using expression beans like ${departmentResolver.getAssignee(execution)} are really easy to use, but they are not first-class expression citizens. They require the execution of a method call and passing execution as a parameter into the method. To avoid this step use a simple assignee expression format, ${departmentAssignee}. This requires extending the expression resolvers (ELResolvers) in the engine’s expression manager.

@Bean
public SpringProcessEngineConfiguration processEngineConfiguration(ApplicationContext applicationContext, DataSource dataSource, PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration configuration =
new SpringProcessEngineConfiguration();
configuration.setDataSource(dataSource);
configuration.setTransactionManager(transactionManager);
configuration.setDatabaseSchemaUpdate("true");

configuration.setExpressionManager(new CustomExpressionManager(applicationContext, Collections.emptyMap()));

return configuration;
}

The CustomExpressionManager extends a set of available ELResolvers with a new department specific ELResolver.

public static class CustomExpressionManager extends SpringExpressionManager {

@Override
protected ELResolver createElResolver(VariableContainer variableContainer) {
CompositeELResolver compositeElResolver = new CompositeELResolver();
compositeElResolver.add(createVariableElResolver(variableContainer));
compositeElResolver.add(createSpringElResolver());
compositeElResolver.add(new ArrayELResolver());
compositeElResolver.add(new ListELResolver());
compositeElResolver.add(new MapELResolver());
compositeElResolver.add(new JsonNodeELResolver());
compositeElResolver.add(new BeanELResolver());

compositeElResolver.add(new DepartmentELResolver(variableContainer));

compositeElResolver.add(new CouldNotResolvePropertyELResolver());
return compositeElResolver;
}
}

The DepartmentELResolver resolves the "departmentAssignee" keyword and returns its associated department specific value.

public static class DepartmentELResolver extends VariableContainerELResolver {

public DepartmentELResolver(VariableContainer variableContainer) {
super(variableContainer);
}

@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null && "departmentAssignee".equals(property) && variableContainer instanceof ExecutionEntity) {
Object departmentId = variableContainer.getVariable("departmentId");
if (departmentId == null) {
throw new RuntimeException("departmentId was not found in execution " + ((ExecutionEntity) variableContainer).getId());
}
context.setPropertyResolved(true);
return getDepartmentAssignee(departmentId);
}
return null;
}

protected String getDepartmentAssignee(Object departmentId) {
return departmentId + ".assignee";
}

@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
}

The process model user task specification is now:

<userTask id="theTask" name="My Task" flowable:assignee="${departmentAssignee}"/>

For further reading and to get more inspired have a look at the set of ELResolvers in CustomExpressionManager#createElResolver.

Setting Values

Up until this point, all the expression examples only return values. However, expressions can be used to change values too. For example, given this human task case model:

<case id="oneHumanTaskCase">
<casePlanModel id="myPlanModel" name="My CasePlanModel">
<planItem id="planItem1" name="The Task" definitionRef="theTask" />
<humanTask id="theTask" name="The Task" isBlocking="true" flowable:assignee="johnDoe"/>
</casePlanModel>
</case>

It is possible to update a case instance variable without any expression as demonstrated by this JUnit test:

@Test
@CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn")
public void testOneHumanTaskCaseScopeExpression() {
// create case instance with one human task
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
.caseDefinitionKey("oneHumanTaskCase")
// create a variable and set its value. Default expression manager allows variable update.
.variable("variableToUpdate", "VariableValue")
.start();
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();

// complete task and update variable in the case instance
cmmnTaskService.complete(task.getId(), Collections.singletonMap(
"${variableToUpdate}", "updatedVariableValue"
)
);
HistoricCaseInstance historicCaseInstance = cmmnHistoryService
.createHistoricCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeCaseVariables().singleResult();
assertThat(historicCaseInstance.getCaseVariables().get("variableToUpdate"), is("updatedVariableValue"));
}

The code adds the variable, variableToUpdate, to the case with the value VariableValue. At the completion of the task VariableContainerELResolver resolves the expression ${variableToUpdate} and sets a new value for the variable, updatedVariableValue. The assertThat() verifies this in fact happened.

More expression setting value examples are in CmmnTaskServiceTest.