BPMN (Processes)
In Toqio Workflow Design, a process is a predefined sequence of steps or tasks designed to achieve a specific goal. It's like a blueprint that defines how work flows through different activities, decisions, and roles within an organization.
Think of a process as a flowchart that guides how certain tasks are carried out, such as handling a customer order, approving a document, or onboarding a new employee. In workflows, these processes are automated, allowing you to create efficient, repeatable flows that ensure the right tasks are completed by the right people or systems.
Workflows use BPMN 2.0 (Business Process Model and Notation), a graphical representation to model these processes. BPMN diagrams visually depict the steps in a process, such as:
- Tasks (e.g., user tasks, automated tasks, scripts, HTTP calls),
- Events (e.g., start, end, or intermediate events)
- Gateways (e.g., decisions or branching paths)
By defining a process in Toqio Workflow Design, you can streamline business operations, automate repetitive tasks, and ensure that processes are executed consistently, reducing manual effort and errors. Key benefits of workflow processes include:
- Automation: Simplify complex workflows by automating routine tasks.
- Consistency: Ensure that tasks are always executed in a standardized manner.
- Visibility: Obtain real-time tracking of tasks and processes, making it easier to monitor progress.
- Integration: Connect to external systems, such as databases or web services.
In summary, a process in Toqio Workflow Design is a powerful way to model, automate, and improve the flow of work within an organization. Here’s an example of a workflow process:
A process instance can be launched by an HTTP call (rest buttons in forms) using the following values:
Method: POST
Endpoint: {{endpoints.process}}/runtime/process-instances
Body: (example)
From outside workflows (From BE or Postman): It is necessary to add a bearer token for being authenticated.
{
"processDefinitionKey":"EX_P053",
"variables":[{"name":"customer","type":"string","value":"TOQIO"}]
}
Every process model must start with a start process event and finish with an end event.
Back end expressions
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 include setting dynamic task names, evaluating sequence flows after a gateway, or creating a variable of a certain type. As the name implies, these expressions run on the back end, during execution. As such, they have no access to the front end data at the time of evaluation.
In Toqio Workflow Design, expressions are allowed in fields marked with a lightning bolt.
Toqio Workflow Design 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()}.
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 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).
Process instance properties
The following properties are available on a process instance, for example when using root or parent.
Example usage: ${root.businessKey}.
Property | Description |
---|---|
businessKey | The business key of the process instance, if set. |
businessStatus | The business status of the process instance, if set. |
callbackId | If 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. |
description | The description of the process instance, if set. |
definition | The process definition. See below for more details. |
id | The unique technical identifier of the process instance. |
name | The name of the process instance, if set. |
startTime | The time (as a java.util.Date) this process instance was started. |
startUserId | The id of the user that started this process instance. |
tenantId | The tenant identifier of this process instance. |
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 they can be done on a keyword object (depending on the context):
- ${execution.setVariable(varName, varValue)} sets the value varValue to the variable varName of the current process instance.
- ${caseIstance.setVariable(varName, varValue)} sets the value varValue to the variable varName of the current case instance.
- ${planItemInstance.setVariable(varName, varValue)} sets the value varValue to the variable varName of the current case instance.
- ${task.setVariable(varName, varValue)} sets the value varValue to the variable varName of the current process/case instance.
Function | Description |
---|---|
${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 behaviour 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 behaviour 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 (regularly 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 |
String utilities
String manipulation methods are a classic use case for expressions. The following methods are available:
Expression | Description |
---|---|
${flwStringUtils.carriageReturn()} | Return 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 string contains another string. Supported values are String, a JSON TextNode or null. |
${flwStringUtils.containsIgnoreCase(text, otherText)} | Checks if a string contains another string ignoring the case. Supported values are String, a JSON TextNode or null. |
${flwStringUtils.equals(text, otherText)} | Checks if two strings are 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)} | Turn all characters of the string to lowercase. |
${flwStringUtils.toUpperCase(text)} | Turn 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. |
Formatting utilities
The following methods are available if you need to format values:
Expression | Description |
---|---|
${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 .
Date and time utilities
Expression | Description |
---|---|
${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 a 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.parseIso8601String(String value)} | Parses the given ISO8601 formatted string and returns it as either an Instant, LocalDate or LocalDateTime, depending on the formatted string content. |
${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. |
JSON utilities
The following functions are available when working with JSON objects in expressions. They work in BPMN and CMMN contexts.
Expression |
---|
${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. |
Mathematical utilities
The following methods are available when you need to do mathematical operations:
Expression | Description |
---|---|
${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. |
Components {#components}
Initialize variables
To initialize a variable, the initialize variable task can be used. This task allows you to create a new variable or overwrite an existing one.
Script task
In Workflows, script tasks are used to execute scripts in a JSR-223 compatible scripting language, such as Groovy.
Script tasks are mainly used to perform simple calculations or operations. For more complex use cases, you should consider writing a Java service and calling it via a service task.
In the following example, a user is prompted to enter a first and last name.
A script generates an e-mail address and stores it in the variable "email":
The following example shows a very simple sample script that could be used to achieve this task:
var firstName = execution.getVariable("firstName").toLowerCase();
var lastName = execution.getVariable("lastName").toLowerCase();
var email = firstName + "." + lastName + "@yourCompany.com";
execution.setVariable("email", email);
Exclusive gateway
A diverging exclusive gateway (decision) is used to create alternative paths within a process flow. This is basically the "diversion point in the road" for a process. For a given instance of the process, only one of the paths can be taken:
-BPMN 2.0.2 Standard, 10.6.2, Exclusive Gateway
Exclusive gateways are one of the most common sights within a process model. They are used to model decisions within a process.
To decide which route the process follows, the conditions on the outgoing sequence flows are checked. The conditions are modelled as an expression and must always evaluate to a boolean value of true or false. It is important to understand that the conditions of an exclusive gateway are not modelled on the gateway itself but rather on the outgoing sequence flows.
If more than one condition evaluates to true, the path that was defined first is chosen. This should obviously never happen. If no condition evaluates to true, the process is stuck.
There is the possibility to mark a sequence flow as the default flow. If you do so, the flow follows this route if no condition matches.
The following example shows how an exclusive gateway is used to model a simple decision:
Parallel gateway
A parallel gateway is used to synchronize (combine) or create parallel flows. A parallel gateway creates parallel paths without checking any conditions; each outgoing sequence flow receives a token upon execution of this gateway. For incoming flows, the parallel gateway will wait for all incoming flows before triggering the flow through its outgoing sequence flows.
-BPMN 2.0.2 Standard, 10.6.4, Parallel Gateway
Parallel gateways are used to model situations where more than one path is executed in parallel. These gateways do not have any conditions since all paths are always executed. To join parallel branches, use another parallel gateway. The execution only continues once all paths have been completed.
In the following example, the process immediately starts with a parallel gateway which triggers the execution of all three tasks. Once all of them are completed, the process is completed.
HTTP task
The HTTP Task allows the user to submit and store the result of an HTTP call.
This is an example of an HTTP that consults an API that generates a fake user profile:
Call activity
A call activity identifies a point in the process where a global process or a global task is used. The call activity acts as a “wrapper” for the invocation of a global process or global task within the execution.
The activation of a call activity results in the transfer of control to the called global process or global task.
-BPMN 2.0.2 Standard, 10.3.6, Call Activity
BPMN 2.0 makes a distinction between a regular sub-process, often also called embedded sub-process, and the call activity, which looks very similar. From a conceptual point of view, both call a sub-process when the process execution arrives at the activity.
The difference is that the call activity references a process that is external to the process definition, whereas the sub-process is embedded within the original process definition. The main use case for the call activity is to have a reusable process definition that can be called from multiple other process definitions.
Updated 16 days ago