This section contains information on the different ways you may want to customize the Engine for your workflow business case.
Using Business Calendars
The Business Calendar feature provides precise time management within a business process by considering specific working hours, workdays, and holidays. This ensures that time-based events and deadlines are aligned with actual business schedules rather than simple chronological time.
This feature is particularly useful in scenarios where precise timing is critical, such as:
Meeting SLAs: Ensuring that deadlines are honored based on actual working time.
Task Scheduling: Tasks are initiated and completed only during working hours, optimizing resource usage and efficiency.
Deadline Management: Managing deadlines accurately by excluding holidays and out of office hours, avoiding unexpected timing issues.
The key features of the Business Calendar are:
- Working Hours and Days
-
The Business Calendar ensures that tasks are scheduled and executed only during predefined business hours and working days. Any time calculations respect the operational schedule of your organization, preventing tasks from being scheduled during out of work hours.
- Holiday Management
-
This feature allows businesses to incorporate holidays into the calendar, automatically excluding these non-working days from time calculations. As a result, task timings and deadlines are adjusted appropriately to account for these holidays.
- Accurate Time Intervals
-
The Business Calendar calculates accurate time intervals between tasks by excluding non-working hours and holidays. This ensures that process deadlines and task delays reflect the actual business operations, which is critical for meeting service-level agreements (SLAs) and managing deadlines.
Enabling Business Calendar functionality
To enable the Business Calendar functionality in your application, follow these steps:
-
Create a
calendar.propertiesfile in thesrc/main/resourcesdirectory of your file. This file activates the Business Calendar feature and the required configuration. The default calendar is:Working Days: Monday - Friday Working Hours: 9 AM - 5 PM Holidays: None Weekend Days: Saturday and Sunday -
Add the properties to the file to change the default settings for those properties you wish to change, for example:
business.end.hour=16
business.start.hour=8
business.holiday.date.format=yyyy-MM-dd
business.holidays=2024-11-07
business.weekend.days=1,7
business.cal.timezone= America/Los_Angeles
The following table provides more details on each property. Adhering to these guidelines will help ensure that tasks are executed as expected. Incorrect configurations may result in unintended behavior.
| Property | Description | Default | Type |
|---|---|---|---|
|
The hour of the day when business starts. Valid range 0-23 |
9 |
Integer |
|
The hour of the day when business ends. The end hour must be greater than start hour. Valid range 0-23, |
17 |
Integer |
|
The number of hours worked per day is calculated by subtracting the |
8 (calculated) |
Integer that is never specified directly |
|
Days considered as weekends represented by one or more integers: range is |
7,1 (Saturday and Sunday) |
Integer |
|
The number of days worked per week cannot be directly specified. It is calculated by subtracting the number of busines.weekend.days from 7. |
5 (calculated) |
Integer that is never specified directly |
|
The date format to indicate holiday time. Input must match defined format |
yyyy-MM-dd |
String |
|
Dates of holiday time to be taken into account in the process. Holiday days can be specified as individual dates (e.g., 2024-12-25,2024-12-31) or as a range of dates (e.g., 2024-11-12:2024-11-14). Dates must be specified in the format defined by business.holiday.date.format. |
empty (no defined vacation time) |
String |
|
The timezone to be taken into account for process scheduling can be changed from the timezone of the system. The time-zone must be a valid timezone according to https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html. |
the default timezone of the system |
String |
Customizing your Business Calendar
The custom Business Calendar feature allows you to define your own scheduling rules, overriding the BusinessCalendarImpl provided out of the box. This feature can be useful when you have unique time-based constraints that differ from the default setup. For example, if you need to:
-
modify scheduling rules to match specific business needs
-
override or extend default time calculations
-
customize task scheduling and deadline management based on unique work schedules
-
ensure process deadlines respect specific business operations
To create and integrate a Custom Business Calendar, follow these steps:
-
Navigate to the root directory within the
process-business-calendarexample application -/src/main/java/org/kie/kogito/calendar/custom -
Create the
org/kie/kogito/calendar/custom directoryif it does not exist -
Create custom calendar class (e.g., CustomCalendar.java)
NoteEnsure it implements the BusinessCalendarinterface. The implementation should be a concrete class, not an interface or abstract class.An example is provided below. However, you are free to define your own logic.
package org.kie.kogito.calendar.custom; import java.util.*; import org.kie.kogito.calendar.BusinessCalendar; + /** * Custom Business Calendar example. * Modify this class to implement your own scheduling logic. */ public class CustomCalendar implements BusinessCalendar { @Override public long calculateBusinessTimeAsDuration(String timeExpression) { // Implement custom logic to calculate business time duration // NOTE: The returned value is in milliseconds (ms). Duration can be set to at least 1000ms or longer to prevent immediate execution. return 1000; } @Override public Date calculateBusinessTimeAsDate(String timeExpression) { // Implement custom logic to return the scheduled date return new Date(); } } -
Configure
src/main/resources/application.propertiesto register Custom Business Calendar to the fully qualified class name of custom Business Calendar.kogito.processes.businessCalendar= org.kie.kogito.calendar.custom.CustomCalendar -
If a
calendar.propertiesfile is present, delete it to remove the default Business Calendar Configuration. The custom implementation will replace the default functionality using theBusinessCalendarImplprovided out of the box.
Testing the custom Business Calendar
After implementing a custom Business Calendar, verify that it works correctly by following these steps:
-
Compile and Run the Application, executing the following Maven command to build and start the application:
mvn clean compile quarkus:dev -
Verify the generated source:
target/generated-sources/kogito/org/kie/kogito/app/BusinessCalendarProducer.javato ensure that it contains the expected changes. If successful,CustomCalendarclass should reflect in theBusinessCalendarProducer.java. For example:
public class BusinessCalendarProducer {
private static final Logger logger = LoggerFactory.getLogger(BusinessCalendarProducer.class);
private BusinessCalendar businessCalendar;
public BusinessCalendarProducer() {
// we can see the CustomCalendar being reflected here
this.businessCalendar = new org.kie.kogito.calendar.custom.CustomCalendar();
}
@Produces
public BusinessCalendar createBusinessCalendar() {
return this.businessCalendar;
}
}
Configuring User Task assignment strategies
User Tasks can be assigned to a single User based on a defined logic. The default logic is defined in the BasicUserTaskAssignmentStrategy class. The default algorithm will automatically assign ANY User Tasks in ACTIVATE status to the resulting person that is part of the “Potential Users” group associated to that User Task and that is NOT part of the “Excluded Users” group associated to the same User Task. In other words, the algorithm performs a Set Difference between the Potential Users and the Excluded User of a given User Task. If that difference results in a single user, the User Task is automatically assigned to that user and moved to “CLAIM” status. In all other cases, the User Tasks is not assigned and no changes are applied on it.
You can implement your specific business logic to automatically assign User Tasks by creating a class that implements the UserTaskAssignmentStrategy interface. The interface defines a single method, computeAssignment, which returns an optional user ID to whom the task should be assigned:
public interface UserTaskAssignmentStrategy {
static final String DEFAULT_NAME = "default";
default String getName() {
return getClass().getName();
}
Optional<String> computeAssignment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider);
}
The logic inside computeAssignment can use task metadata, user roles, or any other contextual information. The following example demonstrates a simple role-based assignment strategy based on the task name.
import org.kie.kogito.process.workitem.UserTaskAssignmentStrategy;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CustomUserTaskAssignmentStrategy implements UserTaskAssignmentStrategy {
@Override
public Optional<String> computeAssignment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) {
System.out.println("Computing assignment using custom User Task assignment strategy.");
// Your custom logic goes here. For example:
if ("hr_interview".equals(userTaskInstance.getTaskName())) {
return Optional.of("recruiter");
} else if ("it_interview".equals(userTaskInstance.getTaskName())) {
return Optional.of("developer");
} else {
return Optional.empty();
}
}
}
One use case may be to assign a specific person as a default when there are multiple potential users for a user task. You can do this by overriding the getName() method in the following way:
public String getName()
{ return DEFAULT_NAME; }
If you need more flexibility in your customization you can create a new Java Class that extends the BasicUserTaskAssignmentStrategy class. The custom assignment logic is wrapped in the inherited computeAssignment method, which replaces the default assignment strategy.
For example:
@Specializes
@ApplicationScoped
public class CustomUserTaskAssignmentStrategy extends BasicUserTaskAssignmentStrategy {
@Override
public Optional<String> computeAssignment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) {
/*
YOUR CUSTOM LOGIC TO AUTOMATICALLY ASSIGN THE USER TASK
*/
}
}
Creating a custom WorkItemHandler
This section provides a step-by-step guide for implementing a custom WorkItemHandler in BAMOE, handler definition, and integration with a BPMN process.
A Work Item represents a task or activity in a workflow that typically involves external operations, such as calling REST APIs, performing database operations, or consuming SOAP web services. A Custom Work Item Handler (WIH) is a user-defined Java class that implements the logic for executing such tasks. It extends the capabilities of standard BPMN tasks (like Service Tasks) by allowing you to plug in custom code to meet specific business requirements not covered by built-in functionality.
Custom Tasks can also define visual and runtime properties for the node on the process model, enhancing both modeling and execution flexibility.
Add the org.kie.kogito:kogito-api dependency to your project POM to set up your Business Service project created using Accelerators. This dependency enables the necessary Kogito APIs required for further configuration and development. For more information see org.kie.kogito:kogito-api external documentation.
Implement the custom WorkItemHandler
To implement the custom logic, create a Java class named MyConcatWorkItemHandler. This class should extend org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler dependency and override the following lifecycle methods to define the behavior of your Custom Task:
-
activateWorkItemHandler(...)the implementation logic for executing the custom task. -
abortWorkItemHandler(...)the logic for handling abort scenarios of the custom task, such as cleanup.
import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
import org.kie.kogito.internal.process.workitem.WorkItemTransition;
import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler;
import org.slf4j.LoggerFactory;
public class MyConcatWorkItemHandler extends DefaultKogitoWorkItemHandler {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(MyConcatWorkItemHandler.class);
@Override
public Optional<WorkItemTransition> activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
for (String parameter : workItem.getParameters().keySet()) {
LOG.info(parameter + " = " + workItem.getParameters().get(parameter));
}
Map<String, Object> results = new HashMap<String, Object>();
String firstName = (String) workItem.getParameter("FirstName");
String lastName = (String) workItem.getParameter("LastName");
String fullName = firstName + " " + lastName;
results.put("FullName", fullName);
return Optional.of(handler.completeTransition(workItem.getPhaseStatus(), results));
}
@Override
public Optional<WorkItemTransition> abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
LOG.info("Aborting work item " + workItem);
return Optional.of(this.workItemLifeCycle.newTransition("abort", workItem.getPhaseStatus(), workItem.getResults()));
}
}
Registering the custom WorkItemHandler
To use a custom WorkItemHandler in a process instance, you must register it before the process starts. This registration informs the engine which handler to invoke for a given task.
Register your handler by extending DefaultWorkItemHandlerConfig in the following way:
import org.kie.kogito.process.impl.DefaultWorkItemHandlerConfig;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CustomWorkItemHandlerConfig extends DefaultWorkItemHandlerConfig {
{
register("MyConcatDefinitions", new MyConcatWorkItemHandler());
}
}
Creating WID files and icons
Work Item Definitions must be provided as .wid files. In order for the custom task definition .wid file to be loaded by the BPMN Editor it can be placed in one of two locations in your project:
-
The root
global/directory (e.g.,[myProject]/global/sharedCustomTasks.wid), -
The same directory as the BPMN file you are working with (e.g.,
[myProject]/src/main/resources/bpmn/tasks.wid).
Icons for custom tasks can be placed in the same directory as the .wid file.
If the BPMN Editor detects *.wid files in both locations (global/ and directory of the opened BPMN file), the contents of the two files are merged and displayed together in Custom Tasks palette.
The following example illustrates the structure of a .wid file:
[
[
"name" : "MyConcatDefinitions",
"displayName" : "MyConcatDefinitions",
"category" : "myconcatworkitem",
"description" : "",
"defaultHandler": "mvel: new com.ibm.MyConcatWorkItemHandler()",
"parameters" : [
"FirstName" : new StringDataType(),
"LastName" : new StringDataType()
],
"results" : [
"FullName" : new StringDataType()
],
"icon" : "MyConcatDefinitions.png"
]
]
|
Note
|
In some cases, BAMOE Canvas may not immediately recognize the .wid file, causing the custom Work Item to be unavailable in the designer palette. To resolve this, run git add customtask.wid command to ensure the file is tracked. If the issue persists, try reopening the BAMOE project in a new VS Code workspace.
|
Using the custom Work Item in a BPMN process
After your .wid file and handler class are in place, you can integrate the Custom Task into a BPMN process using the BPMN Editor.
To add the Custom Task into a BPMN process, do the following steps:
-
Drag and drop the Custom Task onto the BPMN editor canvas.
-
Create process variables in the Process Data section. The variables store input values when starting the process and they must match the parameters in the
.widfile. -
Set the Data Input and Data Output Assignments. This ensures inputs are passed to the handler and outputs are saved to process variables.
Build and run in local Dev mode
After you implement your custom WorkItemHandler and integrate your Custom Task in a Workflow, to build and run in local Dev mode, do the following steps:
-
Run the following command to start your Business Service in Quarkus Dev mode and test changes using hot reload:
mvn clean compile quarkus:dev
+ NOTE: Dev mode allows hot reload of business assets like processes, rules, decision tables, and Java code. You do not need to restart the application.
+ If you are using Quarkus with Gradle, to build and start the project with hot reload enabled, run the following command:
+
gradlew clean quarkusDev
+
If you are using Spring Boot with Gradle, to enable hot reload, add the org.springframework.boot:spring-boot-devtools dependency to the dependencies section of your build.gradle. For more information, see org.springframework.boot:spring-boot-devtools external documentation.
-
Start continuous compilation in one terminal, by using the following command:
gradlew clean compileSecondaryJava --continuous --parallel -
In a second terminal, run the Spring Boot application by using the following command:
gradlew bootRunNoteThe cleantask is not invoked.
Submit a request
To invoke the process, send a POST request to http://localhost:8080/CustomTaskDemo with the following payload:
{
"FirstName": "Alex",
"LastName": "Tom"
}
Example using curl:
curl -X 'POST' \
'http://localhost:8080/CustomTaskDemo' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"FirstName": "Alex",
"LastName": "Tom"
}'
Expected output
After submitting the request, you should see log output similar to the following example:
2025-06-13 13:18:15,826 INFO [com.sample.MyConcatWorkItemHandler:22] (executor-thread-1) FirstName = Alex
2025-06-13 13:18:15,826 INFO [com.sample.MyConcatWorkItemHandler:22] (executor-thread-1) LastName = Tom
2025-06-13 13:18:15,826 INFO [com.sample.MyConcatWorkItemHandler:22] (executor-thread-1) TaskName = MyConcatDefinitions
2025-06-13 13:18:15,826 INFO [com.sample.MyConcatWorkItemHandler:22] (executor-thread-1) NodeName = MyConcatDefinitions
2025-06-13 13:18:15,826 INFO [com.sample.MyConcatWorkItemHandler:22] (executor-thread-1) UNIQUE_TASK_ID = _E2D196C3-0F09-490A-9E26-F4F4BA04BD8C
Full name:Alex Tom
Using sub-processes from a different project
You can use Maven dependent modules to be able to reference a sub-process defined in a different project. Code generation in BAMOE happens during the Business Services build, and using Maven you can ensure that the models are available inside the Class Loader as each model is discovered. To do this:
-
Create a "subprocess-project" that contains the BPMN model(s) to be referenced, in the usual project structure.
Figure 1. subprocess-project source -
Include the minimal Maven dependencies in the project POM to avoid generating classes that could clash with the ones generated in the Business Services project that will reference "subprocess-project", e.g.,
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ibm.bamoe</groupId> <artifactId>subprocess-project</artifactId> <version>...</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project> -
Build "subprocess-project" as a standard Jar, in order to be used as a Maven dependency.
-
Create a "my-business-service" Business Services project that references the Workflows contained in "subprocess-project", by:
-
Adding the
subprocess-projectas a dependency. -
Adding the
target/generated-resourceas resource folder in the build configuration. -
Introducing and configuring the
maven-dependency-pluginso that it extracts the model(s) in thetarget/generated-resourcesfolder during thegenerate-resourcesphase, as shown in the following POM file:<dependencies> <!-- add the “subprocess” module as dependency --> <dependency> <groupId>com.ibm.bamoe</groupId> <artifactId>subprocess-project</artifactId> </dependency> ... <build> <finalName>${project.artifactId}</finalName> <resources> <resource> <directory>src/main/resources</directory> </resource> <!-- add the target/generated-resources as folder resource directory in the build configuration --> <resource> <directory>${project.build.directory}/generated-resources</directory> </resource> </resources> <plugins> ... <!-- configure the maven-dependency-plugin so that it extracts the model(s) in the target/generated-resources folder --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>${dependency-plugin.version}</version> <executions> <execution> <id>unpack</id> <phase>generate-resources</phase> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.ibm.bamoe</groupId> <artifactId>subprocess-project</artifactId> <version>...</version> <type>jar</type> <overWrite>true</overWrite> <outputDirectory>${project.build.directory}/generated-resources</outputDirectory> <includes>**/*.bpmn</includes> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
-
-
Build the "my-business-service" project (see Building Business Service executables) ensuring that the built "subprocess-project" is available for Maven in the usual way.
-
At the start of the build Maven will retrieve the "subprocess-project" artifact during the resolution phase.
-
During the
generate-resourcesphase Maven will unpack the artifact and extract all the*.bpmnfiles under thetarget/generated-resourcesfolder. -
BAMOE will discover the models in both the
src/main/resourcesandtarget/generated-resourcesfolders, and the Workflow Engine will execute code-generation for models present in both resource directories
-
Using this process, at runtime, the code generated for processes defined in the "my-business-service", will be able to find the code generated for the processes imported from the “subprocess-project” artifact.