Workflow expressies syntax
Concept
Workflow expressies worden geschreven in JUEL (Java Unified Expression Language), een implementatie van de Jakarta Expression Language. Camunda 7 gebruikt JUEL als expressietaal in BPMN-processen en voegt er eigen variabelen en services aan toe.
Klik hier om de officiële documentatie van JUEL te consulteren (let wel: niet alles wordt ondersteund).
Een expressie is pas uitvoerbaar als die tussen accolades genoteerd staat. Het geheel wordt door een $ dollarteken voorafgegaan.
${expressie}
Via method chaining kan je meerdere elementen in een logisch verband onderbrengen.
${voornaam.concat(' ‘).concat(achternaam)}
Expressies bieden slechts beperkte functionaliteit. Heb je nood aan meer complexiteit (bijvoorbeeld itereren over lijstelementen)? Dan kan je gebruik maken van scripts.
Wil je een apart stukje code opstellen en deze vervolgens oproepen via een workflow expressie? Kijk dan onder de sectie extensies en customizaties.
Data types
Primitieve waardes
Volgende primitieve data types zijn ondersteund binnen workflow expressies.
Data type | Voorbeeld waarde |
|---|---|
Null | Geen waarde |
String | 'Hello World!' |
Boolean |
|
Integer | 15 (geheel getal, negatief of positief) |
Double | 15.5 (decimaal getal, negatief of positief) |
Datum | 2025-04-01T13:49:13 (altijd inclusief tijdsstempel) |
Opmerkingen:
Een datum literal kan je niet rechtstreeks noteren in een expressie. Gebruik een van de datum-functies hieronder.
De tabel toont de meest gebruikte types. Java kent ook Long, Float en andere numerieke types, maar die komen in de praktijk zelden voor binnen Skryv-configuraties.
Data objecten
Naast primitieve waarden kunnen procesvariabelen ook complexe datastructuren bevatten.
Data structuur | Voorbeeld opbouw |
|---|---|
Lijst | ['value1','value2','value3'] |
Map | {“key1”:'value1',“key2”:'value2',“key3”:'value3'} |
Lijst-Map | [{“key1”:'value1',“key2”:'value2',“key3”:'value3'},{“key1”:'value1',“key2”:'value2',“key3”:'value3'},{“key1”:'value1',“key2”:'value2',“key3”:'value3'}] |
Omgaan met data objecten
Procesvariabelen met een complexere structuur zoals lijsten, maps en lijst-maps kan je op volgende manieren manipuleren vanuit een expressie. De mogelijkheden zijn erg beperkt. Via een workflow script kan je extra zaken doen zoals bijvoorbeeld waardes toevoegen aan of verwijderen uit een lijst.
Waarde ophalen
Je kan een waarde ophalen via volgende expressie.
${mijnVariabele} retourneert de waarde van de procesvariabele mijnVariabele.
Waarde uit een lijst ophalen
Je kan een waarde uit een lijst ophalen via de index.
${mijnLijst[0]} retourneert het eerste item in de lijst.
Aantal items in een lijst vaststellen
Lengte van een lijst vaststellen.
mijnLijst = ['jan','mieke','paul']
${mijnLijst.size()} retourneert het aantal items in de lijst als integer (bijvoorbeeld 3).
Waarde uit een map ophalen
Je kan een waarde uit een map ophalen via de key.
${mijnMap['leeftijd']} retourneert de waarde van de key ‘leeftijd’ in de map.
Waarde uit een lijst-map ophalen
Je kan een waarde uit een lijst-map ophalen via combinatie van index en key.
${mijnLijstMap[0]['leeftijd']} retourneert de waarde van de key ‘leeftijd’ uit het eerste item in de lijst-map.
Operatoren
Booleaanse operatoren
Operator | Beschrijving | Data types |
|---|---|---|
&& | Verbindt twee booleaanse waardes. Indien de twee waardes Tekstueel equivalent: | Boolean |
|| | Verbindt twee booleaanse waardes. Vanaf het ogenblik dat één van beide waardes (of allebei) Tekstueel equivalent: | Boolean |
! | Negeert een booleaanse waarde. Tekstueel equivalent: | Boolean |
empty | Retourneert | Boolean |
Vergelijkende operatoren
Operator | Beschrijving | Data types |
|---|---|---|
== | Vergelijk twee waardes met elkaar. Indien identiek, retourneert de expressie Tekstueel equivalent: | String, boolean, number, date |
!= | Vergelijk twee waardes met elkaar. Indien identiek, retourneert de expressie Tekstueel equivalent: | String, boolean, number, date |
< | Vergelijkt twee waardes met elkaar. Indien waarde A kleiner dan waarde B, retourneert de expressie Tekstueel equivalent: | Number, date |
> | Vergelijkt twee waardes met elkaar. Indien waarde A groter dan waarde B, retourneert de expressie Tekstueel equivalent: | Number, date |
<= | Vergelijkt twee waardes met elkaar. Indien waarde A kleiner of gelijk aan waarde B, retourneert de expressie Tekstueel equivalent: | Number, date |
>= | Vergelijkt twee waardes met elkaar. Indien waarde A groter of gelijk aan waarde B, retourneert de expressie Tekstueel equivalent: | Number, date |
Opmerking: in XML-contexten (zoals BPMN-bestanden) is het soms beter de tekstuele alternatieven gebruiken: and, or, eq, ne, lt, gt, le, ge — om te vermijden dat symbolen zoals < of & geëscaped moeten worden.
Empty check of null-check
Controleer of een procesvariabele een waarde heeft:
${mijnVariabele != null} // true als de variabele een waarde heeft
${mijnVariabele == null} // true als de variabele geen waarde heeft
Voor strings kan je ook empty gebruiken, dat zowel null als lege strings opvangt:
${not empty mijnVariabele}
Logische statements
Statement | Beschrijving |
|---|---|
[boolean] ? [functie A] : [functie B] | Indien boolean |
Functies
Camunda engine services
Functies worden aaneengeregen tot expressies via de Fluent API methodiek (method chaining). Meer details voor courant gebruikte Camunda engine services.
Skryv platform services
Functies worden aaneengeregen tot expressies via de Fluent API methodiek (method chaining). Meer details voor courant gebruikte Skryv platform services.
Datum tijd functies
Datum en tijd ophalen
Hiervoor heb je twee mogelijke expressies:
${now()}${dateTime().toDate()}
Deze resulteren in een java.util.Date-object met datum waarde als 2025-04-01T13:49:13.
Datum en tijd instellen
Ook hiervoor heb je verschillende mogelijkheden. Onderstaande is er één van.
${dateTime().withDate(2030,12,30).toDate()}
Het resultaat is een datum waarde 2030-12-30T09:24:30, waarbij de tijdsstempel gelijk is aan het ogenblik waarop de variabele aangemaakt wordt.
Wil je ook de tijdsstempel preciseren, dan voeg je .withTime() method toe.
${dateTime().withDate(2030,12,30).withTime(12,0,0,0).toDate()}
Opgelet, de workflow engine zit in tijdszone UTC, en een tijdsstempel zetten is dus vanuit UTC tijd gezien. Doordat België zich in de winter in UTC+1 bevindt, zal de resulterende datum waarde 2030-12-30T13:00:00 zijn. In de zomer bevindt België zich in UTC+2, dus zal de resulterende datum waarde bijvoorbeeld 2030-06-30T14:00:00 zijn.
Datum en tijd componenten ophalen
Functie met voorbeeld | Beschrijving |
|---|---|
| Retourneert het jaar als integer. |
| Retourneert de maand als integer. |
| Retourneert de dag als integer. |
| Retourneert het uur als integer. |
| Retourneert de dag van de week als integer (1=maandag, 7=zondag). |
Opgelet: hiervoor moet mijnDatum van het type datum zijn, niet van het type string. Gebruik indien nodig eerst dateTime().parse(mijnDatum) om te converteren.
Datumveld uit formulier omzetten naar een datum waarde
Dit kan je doen in twee stappen:
Stap 1: Breng de waarde van het datumveld formulier over naar een procesvariabele zoals
mijnDatum. De waarde is nu evenwel van het type string.Stap 2: Zet daarom nu de procesvariabele om naar een datum waarde via volgende expressie
${dateTime().parse(mijnDatum).toDate()}
Het resultaat is een datum waarde, bijvoorbeeld: 2025-04-01T00:00:00
Datumwaarde wegschrijven naar een datumveld in formulier
Een procesvariabele (bijvoorbeeld ‘test’) met een datum waarde kan je via de setField functie in het formulierveld (bijvoorbeeld ‘datum’) wegschrijven.
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("testFormulierC").setField("datum",test)}
De tijdsstempel wordt achterwege gelaten en de datum wordt geformatteerd als string-waarde.
Rekenen met datum tijd binnen workflow
Binnen de scope van de workflow is het best practice om timer events te gebruiken. Enkele voorbeelden van use cases: na weigering krijgt de aanvrager nog 30 dagen de mogelijkheid om beroep aan te tekenen, twee dagen vóór de opleveringsdatum van een taak wordt een reminder uitgestuurd naar de uitvoerder van de taak, enzovoort. Klik hier voor meer uitleg over timer events, hun gedrag en eigenschappen.
Rekenen met datum tijd binnen expressies
Binnen de scope van een expressie kan je volgende functies aanspreken.
Functie met voorbeeld | Beschrijving |
|---|---|
Zelfde opbouw lukt ook met plusYears(), plusMonths(), plusWeeks(), plusHours(), plusMinutes(), plusSeconds(). Je kan ook combinaties maken. | Retourneert de huidige datum plus één dag. |
Zelfde opbouw lukt ook met minusYears(), minusMonths(), minusWeeks(), minusHours(), minusMinutes(), minusSeconds(). Je kan ook combinaties maken. | Retourneert de huidige datum min één dag. |
Alternatief: | Retourneert |
Alternatief: | Retourneet |
Alternatief: | Retourneert |
Opgelet: hiervoor moet datum van het type datum zijn, niet van het type string. Gebruik indien nodig eerst dateTime().parse(datum) om te converteren.
Getal functies
Overzicht van functies die je kan uitvoeren op een variabele van het type getal.
Operator | Beschrijving |
+ | Som van twee getalwaardes. |
- | Verschil van twee getalwaardes. |
/ | Quotiënt van twee getalwaardes. |
* | Product van twee getalwaardes. |
Via haakjes kan je verschillende numerieke bewerkingen groeperen en hiërarchisch met elkaar combineren.
${(getalA + getalB) * 3 / 2}
String functies
Overzicht van een aantal courante functies die je kan uitvoeren op een variabele van het type string.
Functie met voorbeeld | Beschrijving |
|---|---|
| Twee of meerdere string waardes met elkaar concateneren. Retourneert ‘Hello world!’. |
| Waarde in procesvariabele omzetten naar een string. |
| Retourneert |
| Retourneert |
| Retourneert |
| Retourneert |
| Retourneert |
| Verwijdert whitespace voor en na en retourneert ‘tekst met whitespace voor en na’. |
| Retourneert ‘jan peeters'. |
| Retourneert ‘JAN PEETERS’. |
| Retourneert ‘Jan Janssen’. |
| Retourneert ‘Ja’. Dit het stuk van de string dat zich uitstrekt van index positie 0 (inclusief) tot index positie 2 (exclusief). |
| Retourneert ‘Peeters’. Dit is het stuk van de string dat zich uitstrekt van index positie 4 tot en met de laatste index positie. |
| Retourneert 11, het aantal karakters in de string als integer. |
| Retourneert de index positie van het eerste voorkomen van het opgegeven karakter of deelstring. Retourneert |
| Retourneert |
Expressie voorbeelden
Voorbeeld 1: default waarde instellen bij lege inputstring
Opgave
${empty referentieNr ? '—' : referentieNr}
Variabelen
{
referentieNr = ""
}
Uitkomst
'—'
Uitleg
De expressie ${empty referentieNr ? '—' : referentieNr} gebruikt twee zaken: (1) de operator empty die true is voor null, '' (lege string), [] (lege lijst) en {} (lege map); en (2) de ternary operator [voorwaarde] ? [A] : [B]. Als referentieNr leeg is, krijg je '—' terug; anders krijg je de waarde van referentieNr. In dit voorbeeld is referentieNr = '' (lege string), dus is empty referentieNr true en wordt de dash '—' weergegeven.
Voorbeeld 2: deadline vergelijken met huidige datum
Opgave
${dateTime().parse(deadline).toDate().before(now())}
Variabelen
{
deadline = "2025-01-01"
}
Uitkomst
true
Uitleg
De expressie ${dateTime().parse(deadline).toDate().before(now())} controleert of de datum in de variabele deadline vóór het huidige moment ligt. Stap voor stap:
deadlineis een string met een datum, bv."2025-01-01".dateTime()geeft een helper-object om met datums/tijden te werken.dateTime().parse(deadline)zet de string om naar een intern datum/tijd-object (1 januari 2025 om 00:00:00 in de ingestelde tijdzone)..toDate()converteert dit naar eenjava.util.Date.now()geeft het huidige moment alsjava.util.Dateterug.before(now())vergelijkt beide datums en istrueals dedeadlinein het verleden ligt t.o.v. nu.
Waarom hier "true"? Omdat "2025-01-01" een datum in het verleden is t.o.v. het moment waarop de expressie geëvalueerd wordt (now()).
Voorbeeld 3: string formatteren (title case)
Opgave
${empty naam ? '' : (naam.toLowerCase().substring(0,1).toUpperCase().concat(naam.toLowerCase().substring(1)))}
Variabelen
{
"naam":"peeters"
}
Uitkomst
"Peeters"
Uitleg
De expressie ${empty naam ? '' : (naam.toLowerCase().substring(0,1).toUpperCase().concat(naam.toLowerCase().substring(1)))} werkt als volgt (stap-voor-stap):
empty naam ? '' : ...— leeg of null? Dan een lege string; anders formatteer verder.naam.toLowerCase()— maak alles kleine letters (bv. "PEETERS" → "peeters")....substring(0,1).toUpperCase()— pak het eerste teken en maak het hoofdletter ("p" → "P").naam.toLowerCase().substring(1)— neem de rest van de string vanaf index 1 ("eeters")..concat(...)— voeg eerste hoofdletter en rest samen: "P" + "eeters" = "Peeters".Beperking: dit capitaliseert enkel het eerste teken van de volledige string, niet elk afzonderlijk woord ("jan peeters" zou resulteren in "Jan peeters").
Voorbeeld 4: check of specifieke optie meerkeuzeveld aangevinkt is
Opgave
Inclusive gateway met expressies op elk van de uitgaande pijlen.
Ontbijt check
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("hotelFormulier").getField("hotelVoorkeuren")['ontbijt'] == true}
Parkeerplaats check
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("hotelFormulier").getField("hotelVoorkeuren")['parkeerplaats'] == true}
Spa check
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("hotelFormulier").getField("hotelVoorkeuren")['spa'] == true}
Variabelen
{
"ontbijt": true,
"parkeerplaats": false,
"spa": true
}
Uitkomst
Pijl Ontbijt: geactiveerd;
Pijl Parkeerplaats: niet geactiveerd;
Pijl Spa: geactiveerd.
Uitleg
Ontbijt: variabele
ontbijt = true, dus de expressie...['ontbijt'] == trueevalueert naartrueen activeert die pijl.Parkeerplaats: variabele
parkeerplaats = false, dus...['parkeerplaats'] == trueisfalse; die pijl blijft uit.Spa: variabele
spa = true, dus...['spa'] == trueistrueen activeert de pijl.
Aangezien het een inclusive gateway is, kunnen meerdere pijlen tegelijk actief zijn; in dit geval Ontbijt en Spa.
Voorbeeld 5: check of aan twee voorwaarden tegelijk is voldaan
Opgave
Exclusive gateway met twee uitgaande pijlen die de twee mogelijke uitkomsten opvangen true of false.
Check indien voldaan aan beide voorwaarden:
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("persoonsFormulier").getField("leeftijd") > 65 && skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("persoonsFormulier").getField("gehuwd") == false}
Check indien niet voldaan aan minstens één van beide voorwaarden:
${skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("persoonsFormulier").getField("leeftijd") <= 65 || skryv.dossierFromScope(execution).getOrCreateDocumentByDefinitionKey("persoonsFormulier").getField("gehuwd") == true}
Uiteraard is het mogelijk om één van beide pijlen aan te duiden als de default. In dat geval is het niet nodig om daar te geïnverteerde expressie te definiëren.
Variabelen
{
"persoonsFormulier.leeftijd": 66,
"persoonsFormulier.gehuwd": true
}
Uitkomst
Pijl
true(beide voorwaarden voldaan): niet geactiveerd.Pijl
false(minstens één voorwaarde niet voldaan): geactiveerd.
Uitleg
Voorwaarde 1: leeftijd > 65?
66 > 65 = true.Voorwaarde 2: gehuwd == false?
gehuwd = true, dus (true == false) = false.Evaluatie TRUE-pijl (AND):
true && false = false→ niet geactiveerd.Evaluatie FALSE-pijl (OR):
(leeftijd <= 65) || (gehuwd == true) = false || true = true→ geactiveerd.
Voorbeeld 6: bedrag berekenen op basis van inkomen en gezinsgrootte
Opgave
${inkomen < 15000 ? (personenTenLaste >= 3 ? 1500 : 1000) : (inkomen < 25000 ? personenTenLaste >= 3 ? 800 : 500) : 0)}
Opmerking: dit is een perfecte use case voor een beslissingstabel. Zeker bij complexere logica.
Variabelen
{
"inkomen": 16000,
"personenTenLaste": 5
}
Uitkomst
800
Uitleg
Stap-voor-stap evaluatie:
Eerste voorwaarde (buitenste ternary links):
inkomen < 15000→16000 < 15000→falseWe nemen dus NIET het stuk
(personenTenLaste >= 3 ? 1500 : 1000), maar gaan naar de rechtertak na de:.
We komen in de rechtertak terecht:
(inkomen < 25000 ? (personenTenLaste >= 3 ? 800 : 500) : 0)
Tweede voorwaarde:
inkomen < 25000→16000 < 25000→trueWe nemen dus het stuk
(personenTenLaste >= 3 ? 800 : 500)en niet0.
Derde voorwaarde (binnenste ternary):
personenTenLaste >= 3→5 >= 3→trueDaarom kiezen we
800(en niet500).
Resultaat van de volledige expressie:
800.