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:

  1. Create a calendar.properties file in the src/main/resources directory 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
  2. 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

business.start.hour

The hour of the day when business starts. Valid range 0-23

9

Integer

business.end.hour

The hour of the day when business ends. The end hour must be greater than start hour. Valid range 0-23,

17

Integer

business.hours.per.day

The number of hours worked per day is calculated by subtracting the business.start.hour from the business.end.hour.

8 (calculated)

Integer that is never specified directly

business.weekend.days

Days considered as weekends represented by one or more integers: range is 1(Sunday), 2(Monday),…​ 7(Saturday). If you want to consider all days as working days, input 0 as value and the resulting calculation of business.days.per.week is 7.

7,1 (Saturday and Sunday)

Integer

business.days.per.week

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

business.holiday.date.format

The date format to indicate holiday time. Input must match defined format

yyyy-MM-dd

String

business.holidays

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

business.cal.timezone

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:

  1. Navigate to the root directory within the process-business-calendar example application - /src/main/java/org/kie/kogito/calendar/custom

  2. Create the org/kie/kogito/calendar/custom directory if it does not exist

  3. Create custom calendar class (e.g., CustomCalendar.java)

    Note
    Ensure it implements the BusinessCalendar interface. 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();
        }
    }
  4. Configure src/main/resources/application.properties to register Custom Business Calendar to the fully qualified class name of custom Business Calendar.

    kogito.processes.businessCalendar= org.kie.kogito.calendar.custom.CustomCalendar
  5. If a calendar.properties file is present, delete it to remove the default Business Calendar Configuration. The custom implementation will replace the default functionality using the BusinessCalendarImpl provided out of the box.

Testing the custom Business Calendar

After implementing a custom Business Calendar, verify that it works correctly by following these steps:

  1. Compile and Run the Application, executing the following Maven command to build and start the application: mvn clean compile quarkus:dev

  2. Verify the generated source: target/generated-sources/kogito/org/kie/kogito/app/BusinessCalendarProducer.java to ensure that it contains the expected changes. If successful, CustomCalendar class should reflect in the BusinessCalendarProducer.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:

  1. The root global/ directory (e.g., [myProject]/global/sharedCustomTasks.wid),

  2. 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:

  1. Drag and drop the Custom Task onto the BPMN editor canvas.

  2. 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 .wid file.

  3. 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:

  1. 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.

  1. Start continuous compilation in one terminal, by using the following command:

    gradlew clean compileSecondaryJava --continuous --parallel
  2. In a second terminal, run the Spring Boot application by using the following command:

    gradlew bootRun
    Note
    The clean task 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:

  1. Create a "subprocess-project" that contains the BPMN model(s) to be referenced, in the usual project structure.

    subprocess-project source
    Figure 1. subprocess-project source
  2. 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>
  3. Build "subprocess-project" as a standard Jar, in order to be used as a Maven dependency.

  4. Create a "my-business-service" Business Services project that references the Workflows contained in "subprocess-project", by:

    • Adding the subprocess-project as a dependency.

    • Adding the target/generated-resource as resource folder in the build configuration.

    • Introducing and configuring the maven-dependency-plugin so that it extracts the model(s) in the target/generated-resources folder during the generate-resources phase, 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>
  5. 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-resources phase Maven will unpack the artifact and extract all the *.bpmn files under the target/generated-resources folder.

    • BAMOE will discover the models in both the src/main/resources and target/generated-resources folders, 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.