You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kie.apache.org by eg...@apache.org on 2024/11/18 13:09:50 UTC

(incubator-kie-kogito-runtimes) branch main updated: [incubator-kie-issues-1551] Deadlines for Human Task (#3752)

This is an automated email from the ASF dual-hosted git repository.

egonzalez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git


The following commit(s) were added to refs/heads/main by this push:
     new a3ac6e7408 [incubator-kie-issues-1551] Deadlines for Human Task (#3752)
a3ac6e7408 is described below

commit a3ac6e7408ccf1aa2c3a5bbaac8b03fca488a318
Author: Enrique <en...@ibm.com>
AuthorDate: Mon Nov 18 14:09:46 2024 +0100

    [incubator-kie-issues-1551] Deadlines for Human Task (#3752)
---
 .../jpa/mapper/AttachmentsEntityMapper.java        |   4 +-
 .../usertask/jpa/mapper/CommentsEntityMapper.java  |   4 +-
 .../org/jbpm/usertask/jpa/mapper/EntityMapper.java |  14 +-
 .../jpa/mapper/TaskDeadlineEntityMapper.java       | 136 ++++++++
 .../jpa/mapper/TaskDeadlineTimerEntityMapper.java  | 132 ++++++++
 .../jpa/mapper/TaskInputsEntityMapper.java         |   4 +-
 .../jpa/mapper/TaskMetadataEntityMapper.java       |   4 +-
 .../jpa/mapper/TaskOutputsEntityMapper.java        |   4 +-
 .../jpa/mapper/TaskReassignmentEntityMapper.java   | 137 ++++++++
 .../mapper/TaskReassignmentTimerEntityMapper.java  | 130 ++++++++
 .../jpa/mapper/UserTaskInstanceEntityMapper.java   |  32 +-
 .../usertask/jpa/mapper/json/utils/JSONUtils.java  |  20 +-
 ...TaskDataEntity.java => AbstractTaskEntity.java} |  37 +--
 .../usertask/jpa/model/TaskDeadlineEntity.java     |  78 +++++
 .../jpa/model/TaskDeadlineTimerEntity.java         |  74 +++++
 .../jbpm/usertask/jpa/model/TaskDeadlineType.java  |  12 +-
 .../jbpm/usertask/jpa/model/TaskInputEntity.java   |  11 +-
 .../usertask/jpa/model/TaskMetadataEntity.java     |  12 +-
 ...askDataEntity.java => TaskNamedDataEntity.java} |  47 +--
 ...ataEntityPK.java => TaskNamedDataEntityPK.java} |  16 +-
 .../jbpm/usertask/jpa/model/TaskOutputEntity.java  |  12 +-
 .../usertask/jpa/model/TaskReassignmentEntity.java |  78 +++++
 .../jpa/model/TaskReassignmentTimerEntity.java     |  73 +++++
 .../usertask/jpa/model/TaskReassignmentType.java   |  12 +-
 ...kDataEntity.java => TaskTimerConfigEntity.java} |  62 ++--
 ...askDataEntity.java => TaskTimerDataEntity.java} |  53 +--
 ...ataEntityPK.java => TaskTimerDataEntityPK.java} |  32 +-
 .../usertask/jpa/model/UserTaskInstanceEntity.java |  92 +++++-
 ...Repository.java => TaskDeadlineRepository.java} |  11 +-
 ...itory.java => TaskDeadlineTimerRepository.java} |  12 +-
 .../jpa/repository/TaskInputRepository.java        |   4 +-
 .../jpa/repository/TaskMetadataRepository.java     |   4 +-
 .../jpa/repository/TaskOutputRepository.java       |   4 +-
 ...sitory.java => TaskReassignmentRepository.java} |  11 +-
 ...y.java => TaskReassignmentTimerRepository.java} |  12 +-
 .../h2/V1.0.1__jBPM_user_task_timers.sql           |  71 ++++
 .../postgresql/V1.0.1__jBPM_user_task_timers.sql   |  72 ++++
 .../usertask/jpa/JPAUserTaskInstancesTest.java     |  17 +-
 .../jpa/mapper/AttachmentsEntityMapperTest.java    |   2 +-
 .../mapper/UserTaskInstanceEntityMapperTest.java   |   8 +-
 .../jbpm/usertask/jpa/mapper/utils/TestUtils.java  |  30 +-
 .../kogito/jobs/api/JobCallbackResourceDef.java    |  43 ++-
 .../jobs/api/JobCallbackResourceDefTest.java       |   4 +-
 .../kogito/jobs/management/RestJobsService.java    |   6 +-
 .../jobs/management/RestJobsServiceTest.java       |  17 +-
 .../src/main/java/org/kie/kogito/StaticConfig.java |  27 +-
 .../java/org/kie/kogito/jobs/JobDescription.java   |   5 +
 .../main/java/org/kie/kogito/jobs/JobsService.java |  12 +-
 .../ProcessInstanceJobDescription.java             |  18 +-
 .../ProcessInstanceJobDescriptionBuilder.java      |   4 +-
 .../{ => descriptors}/ProcessJobDescription.java   |   9 +-
 .../UserTaskInstanceJobDescription.java            |  69 ++++
 .../UserTaskInstanceJobDescriptionBuilder.java     |  80 +++++
 .../java/org/kie/kogito/usertask/UserTask.java     |   8 +-
 .../org/kie/kogito/usertask/UserTaskInstance.java  |  32 ++
 .../usertask/lifecycle/UserTaskLifeCycle.java      |  14 +-
 .../model/{Reassignment.java => Notification.java} |  39 ++-
 .../kie/kogito/usertask/model/Reassignment.java    |  11 +
 .../services/jobs/impl/InMemoryJobContext.java     |  17 +-
 .../services/jobs/impl/InMemoryJobService.java     | 198 +++--------
 .../impl/InMemoryProcessJobExecutorFactory.java    | 189 +++++++++++
 .../impl/InMemoryUserTaskJobExecutorFactory.java   | 123 +++++++
 .../services/jobs/impl/JobExecutorFactory.java     |  23 +-
 .../impl/LegacyInMemoryJobExecutorFactory.java     | 136 ++++++++
 .../jobs/impl/LegacyInMemoryJobService.java        |  83 +----
 .../services/jobs/impl/StaticJobService.java       |  13 +-
 .../services/uow/StaticUnitOfWorkManger.java       |  15 +-
 .../kogito/services/uow/UnitOfWorkExecutor.java    |   1 -
 .../org/jbpm/bpmn2/xml/AbstractNodeHandler.java    |   4 +
 .../org/kie/kogito/process/bpmn2/BpmnProcess.java  |   6 +-
 jbpm/jbpm-flow/pom.xml                             |   5 +
 .../process/instance/AbstractProcessRuntime.java   |   4 +-
 .../jbpm/process/instance/LightProcessRuntime.java |   8 +-
 .../workflow/core/node/AsyncEventNodeInstance.java |   6 +-
 .../org/jbpm/workflow/core/node/HumanTaskNode.java |   2 +-
 .../instance/impl/WorkflowProcessInstanceImpl.java |   6 +-
 .../instance/node/StateBasedNodeInstance.java      |   6 +-
 .../workflow/instance/node/TimerNodeInstance.java  |   6 +-
 .../kie/kogito/process/impl/AbstractProcess.java   |   4 +-
 .../kogito/process/impl/AbstractProcessConfig.java |   7 +-
 .../kogito/process/impl/StaticProcessConfig.java   |  21 +-
 .../src/test/java/org/jbpm/process/TimerTest.java  | 159 ++++-----
 .../jbpm/bpmn2/calendar/BusinessCalendarTest.java  |  12 +-
 .../org/jbpm/test/utils/ProcessTestHelper.java     |  18 +-
 .../handler/UserTaskKogitoWorkItemHandler.java     |  16 +-
 .../kie/kogito/usertask/impl/AbstractUserTask.java |  24 +-
 .../usertask/impl/DefaultUserTaskConfig.java       |  26 +-
 .../usertask/impl/DefaultUserTaskInstance.java     | 259 ++++++++++++++-
 .../kie/kogito/usertask/impl/DefaultUserTasks.java |   2 +
 .../usertask/impl/InMemoryUserTaskInstances.java   |   3 +
 .../kogito/usertask/impl/UserTaskServiceImpl.java  |  16 +-
 .../impl/lifecycle/DefaultUserTaskLifeCycle.java   |  55 +++-
 .../kogito/usertask/impl/model/DeadlineHelper.java |  17 +-
 .../usertask/impl/model/DeadlineHelperTest.java    |  66 ++--
 .../codegen/core/ApplicationConfigGenerator.java   |  22 +-
 .../config/ApplicationConfigJavaTemplate.java      |   2 +-
 .../config/ApplicationConfigQuarkusTemplate.java   |   5 +-
 .../config/ApplicationConfigSpringTemplate.java    |   5 +-
 .../org/kie/kogito/codegen/tests/UserTaskIT.java   | 361 +++++++++++++++++++++
 .../usertask/UserTasksNotCompletedDeadline.bpmn2   | 124 +++++++
 .../usertask/UserTasksNotCompletedReassign.bpmn2   | 124 +++++++
 .../usertask/UserTasksNotStartedDeadline.bpmn2     | 129 ++++++++
 .../usertask/UserTasksNotStartedReasssign.bpmn2    | 124 +++++++
 .../kogito/codegen/usertask/UserTaskCodegen.java   |  25 +-
 .../usertask/UserTaskContainerGenerator.java       |   6 +-
 .../ProcessContainerJavaTemplate.java              |  15 +
 .../usertask/UserTasksContainerJavaTemplate.java   |  13 +
 .../UserTasksContainerQuarkusTemplate.java         |  14 +
 .../usertask/UserTasksContainerSpringTemplate.java |  14 +
 .../StaticFluentWorkflowApplicationTest.java       |  14 +
 ...r.java => QuarkusTaskDeadlineEntityMapper.java} |  14 +-
 ...a => QuarkusTaskDeadlineTimerEntityMapper.java} |  14 +-
 ...va => QuarkusTaskReassignmentEntityMapper.java} |  14 +-
 ... QuarkusTaskReassignmentTimerEntityMapper.java} |  14 +-
 .../QuarkusUserTaskInstanceEntityMapper.java       |  13 +-
 .../repository/QuarkusTaskDeadlineRepository.java  |  23 +-
 .../QuarkusTaskDeadlineTimerRepository.java        |  23 +-
 .../QuarkusTaskReassignmentRepository.java         |  23 +-
 .../QuarkusTaskReassignmentTimerRepository.java    |  23 +-
 .../AbstractReactiveMessagingJobsService.java      |  10 +-
 .../AbstractReactiveMessagingJobsServiceTest.java  |  25 +-
 .../common/CallbackJobsServiceResource.java        |   4 +-
 .../jobs/management/quarkus/VertxJobsService.java  |  11 +-
 .../management/quarkus/VertxJobsServiceTest.java   |   4 +-
 .../quarkus/workflow/KogitoBeanProducer.java       |  10 +-
 ...ava => SpringBootTaskDeadlineEntityMapper.java} |  14 +-
 ...> SpringBootTaskDeadlineTimerEntityMapper.java} |  14 +-
 ...=> SpringBootTaskReassignmentEntityMapper.java} |  14 +-
 ...ringBootTaskReassignmentTimerEntityMapper.java} |  14 +-
 .../SpringBootUserTaskInstanceEntityMapper.java    |  11 +-
 .../SpringBootTaskDeadlineRepository.java}         |  17 +-
 .../SpringBootTaskDeadlineTimerRepository.java}    |  17 +-
 .../SpringBootTaskReassignmentRepository.java}     |  17 +-
 ...SpringBootTaskReassignmentTimerRepository.java} |  17 +-
 .../springboot/CallbackJobsServiceResource.java    |   2 +-
 .../springboot/SpringRestJobsService.java          |  11 +-
 .../springboot/SpringRestJobsServiceTest.java      |   4 +-
 .../.gitignore                                     |   1 +
 .../org/kie/kogito/process/KogitoBeanProducer.java |  16 +
 139 files changed, 3909 insertions(+), 1016 deletions(-)

diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java
index 210553ed85..96aee9ee70 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java
@@ -33,13 +33,14 @@ import org.kie.kogito.usertask.model.Attachment;
 
 import static java.util.stream.Collectors.toCollection;
 
-public class AttachmentsEntityMapper {
+public class AttachmentsEntityMapper implements EntityMapper {
     private final AttachmentRepository repository;
 
     public AttachmentsEntityMapper(AttachmentRepository repository) {
         this.repository = repository;
     }
 
+    @Override
     public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
         Collection<AttachmentEntity> toRemove = userTaskInstanceEntity.getAttachments()
                 .stream()
@@ -65,6 +66,7 @@ public class AttachmentsEntityMapper {
         });
     }
 
+    @Override
     public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
 
         List<Attachment> attachments = userTaskInstanceEntity.getAttachments().stream().map(attachmentEntity -> {
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java
index b854ec3510..d705ef882e 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java
@@ -32,7 +32,7 @@ import org.kie.kogito.usertask.model.Comment;
 
 import static java.util.stream.Collectors.toCollection;
 
-public class CommentsEntityMapper {
+public class CommentsEntityMapper implements EntityMapper {
 
     private final CommentRepository repository;
 
@@ -40,6 +40,7 @@ public class CommentsEntityMapper {
         this.repository = repository;
     }
 
+    @Override
     public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
         Collection<CommentEntity> toRemove = userTaskInstanceEntity.getComments()
                 .stream()
@@ -64,6 +65,7 @@ public class CommentsEntityMapper {
         });
     }
 
+    @Override
     public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
         List<Comment> comments = userTaskInstanceEntity.getComments().stream().map(commentEntity -> {
             Comment comment = new Comment(commentEntity.getId(), commentEntity.getUpdatedBy());
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/EntityMapper.java
similarity index 66%
copy from api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/EntityMapper.java
index 5bbd520bfc..d9759d6e1a 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/EntityMapper.java
@@ -16,13 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.jbpm.usertask.jpa.mapper;
 
-public interface JobDescription {
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.kie.kogito.usertask.UserTaskInstance;
 
-    String id();
+public interface EntityMapper {
 
-    ExpirationTime expirationTime();
+    void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity);
 
-    Integer priority();
-}
+    void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance);
+
+}
\ No newline at end of file
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineEntityMapper.java
new file mode 100644
index 0000000000..7d28bbdbda
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineEntityMapper.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskDeadlineEntity;
+import org.jbpm.usertask.jpa.model.TaskDeadlineType;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JavaType;
+
+public class TaskDeadlineEntityMapper implements EntityMapper {
+    private static final Logger LOG = LoggerFactory.getLogger(TaskDeadlineEntityMapper.class);
+
+    private final TaskDeadlineRepository repository;
+
+    public TaskDeadlineEntityMapper(TaskDeadlineRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+        if (userTaskInstanceEntity.getDeadlines() == null) {
+            userTaskInstanceEntity.setDeadlines(new ArrayList<>());
+        }
+
+        List<TaskDeadlineEntity> entities = new ArrayList<>();
+        List<DeadlineInfo<Notification>> notStartedNotifications = new ArrayList<>();
+        List<DeadlineInfo<Notification>> notCompletedNotifications = new ArrayList<>();
+        Iterator<TaskDeadlineEntity> iterator = userTaskInstanceEntity.getDeadlines().iterator();
+        while (iterator.hasNext()) {
+            TaskDeadlineEntity deadlineEntity = iterator.next();
+            DeadlineInfo<Notification> deadline = readNotification(deadlineEntity.getValue());
+            switch (deadlineEntity.getType()) {
+                case NotCompleted:
+                    if (!userTaskInstance.getNotCompletedDeadlines().contains(deadline)) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notCompletedNotifications.add(deadline);
+                    }
+                    break;
+                case NotStarted:
+                    if (!userTaskInstance.getNotStartedDeadlines().contains(deadline)) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notStartedNotifications.add(deadline);
+                    }
+                    break;
+            }
+        }
+
+        entities.forEach(e -> {
+            repository.remove(e);
+            userTaskInstanceEntity.getDeadlines().remove(e);
+        });
+        entities.clear();
+
+        for (DeadlineInfo<Notification> deadline : userTaskInstance.getNotStartedDeadlines()) {
+            if (!notStartedNotifications.contains(deadline)) {
+                entities.add(buildEntity(userTaskInstanceEntity, deadline, TaskDeadlineType.NotStarted));
+            }
+        }
+        for (DeadlineInfo<Notification> deadline : userTaskInstance.getNotCompletedDeadlines()) {
+            if (!notCompletedNotifications.contains(deadline)) {
+                entities.add(buildEntity(userTaskInstanceEntity, deadline, TaskDeadlineType.NotCompleted));
+            }
+        }
+        entities.forEach(e -> {
+            userTaskInstanceEntity.getDeadlines().add(e);
+        });
+    }
+
+    private TaskDeadlineEntity buildEntity(UserTaskInstanceEntity userTaskInstanceEntity, DeadlineInfo<Notification> deadline, TaskDeadlineType type) {
+        TaskDeadlineEntity entity = new TaskDeadlineEntity();
+        entity.setTaskInstance(userTaskInstanceEntity);
+        entity.setJavaType(DeadlineInfo.class.getName());
+        entity.setValue(JSONUtils.valueToString(deadline).getBytes(StandardCharsets.UTF_8));
+        entity.setType(type);
+        return entity;
+    }
+
+    @Override
+    public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+        List<DeadlineInfo<Notification>> notStarted = new ArrayList<>();
+        List<DeadlineInfo<Notification>> notCompleted = new ArrayList<>();
+
+        for (TaskDeadlineEntity entity : userTaskInstanceEntity.getDeadlines()) {
+            DeadlineInfo<Notification> deadline = readNotification(entity.getValue());
+            switch (entity.getType()) {
+                case NotCompleted:
+                    notCompleted.add(deadline);
+                    break;
+                case NotStarted:
+                    notStarted.add(deadline);
+                    break;
+            }
+        }
+
+        ((DefaultUserTaskInstance) userTaskInstance).setNotStartedDeadlines(notStarted);
+        ((DefaultUserTaskInstance) userTaskInstance).setNotCompletedDeadlines(notCompleted);
+    }
+
+    private DeadlineInfo<Notification> readNotification(byte[] value) {
+        JavaType javaType = JSONUtils.buildJavaType(DeadlineInfo.class, Notification.class);
+        return (DeadlineInfo<Notification>) JSONUtils.stringTreeToValue(new String(value), javaType);
+    }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineTimerEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineTimerEntityMapper.java
new file mode 100644
index 0000000000..568cea2e6f
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskDeadlineTimerEntityMapper.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskDeadlineTimerEntity;
+import org.jbpm.usertask.jpa.model.TaskDeadlineType;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineTimerRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.Notification;
+
+public class TaskDeadlineTimerEntityMapper implements EntityMapper {
+
+    private final TaskDeadlineTimerRepository repository;
+
+    public TaskDeadlineTimerEntityMapper(TaskDeadlineTimerRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public void mapInstanceToEntity(UserTaskInstance instance, UserTaskInstanceEntity userTaskInstanceEntity) {
+        if (userTaskInstanceEntity.getDeadlineTimers() == null) {
+            userTaskInstanceEntity.setDeadlineTimers(new ArrayList<>());
+        }
+        DefaultUserTaskInstance userTaskInstance = (DefaultUserTaskInstance) instance;
+        List<String> notStartedNotifications = new ArrayList<>();
+        List<String> notCompletedNotifications = new ArrayList<>();
+
+        List<TaskDeadlineTimerEntity> entities = new ArrayList<>();
+        Iterator<TaskDeadlineTimerEntity> iterator = userTaskInstanceEntity.getDeadlineTimers().iterator();
+        while (iterator.hasNext()) {
+            TaskDeadlineTimerEntity deadlineEntity = iterator.next();
+            switch (deadlineEntity.getType()) {
+                case NotCompleted:
+                    if (!userTaskInstance.getNotCompletedDeadlinesTimers().keySet().contains(deadlineEntity.getJobId())) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notCompletedNotifications.add(deadlineEntity.getJobId());
+                    }
+                    break;
+                case NotStarted:
+                    if (!userTaskInstance.getNotStartedDeadlinesTimers().keySet().contains(deadlineEntity.getJobId())) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notStartedNotifications.add(deadlineEntity.getJobId());
+                    }
+                    break;
+            }
+        }
+
+        entities.forEach(e -> {
+            repository.remove(e);
+            userTaskInstanceEntity.getDeadlineTimers().remove(e);
+        });
+        entities.clear();
+
+        for (Map.Entry<String, Notification> timer : userTaskInstance.getNotStartedDeadlinesTimers().entrySet()) {
+            if (!notStartedNotifications.contains(timer.getKey())) {
+                TaskDeadlineTimerEntity entity = buildEntity(userTaskInstanceEntity, timer.getKey(), timer.getValue(), TaskDeadlineType.NotStarted);
+                entities.add(entity);
+            }
+        }
+
+        for (Map.Entry<String, Notification> timer : userTaskInstance.getNotCompletedDeadlinesTimers().entrySet()) {
+            if (!notCompletedNotifications.contains(timer.getKey())) {
+                TaskDeadlineTimerEntity entity = buildEntity(userTaskInstanceEntity, timer.getKey(), timer.getValue(), TaskDeadlineType.NotCompleted);
+                entities.add(entity);
+            }
+        }
+        entities.forEach(userTaskInstanceEntity.getDeadlineTimers()::add);
+    }
+
+    private TaskDeadlineTimerEntity buildEntity(UserTaskInstanceEntity userTaskInstanceEntity, String jobId, Notification deadline, TaskDeadlineType type) {
+        TaskDeadlineTimerEntity entity = new TaskDeadlineTimerEntity();
+        entity.setTaskInstance(userTaskInstanceEntity);
+        entity.setJobId(jobId);
+        entity.setJavaType(Notification.class.getName());
+        entity.setValue(JSONUtils.valueToString(deadline).getBytes(StandardCharsets.UTF_8));
+        entity.setType(type);
+        return entity;
+    }
+
+    @Override
+    public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+        Map<String, Notification> notStarted = new HashMap<>();
+        Map<String, Notification> notCompleted = new HashMap<>();
+        for (TaskDeadlineTimerEntity entity : userTaskInstanceEntity.getDeadlineTimers()) {
+            Notification notification = (Notification) JSONUtils.stringTreeToValue(new String(entity.getValue()), Notification.class.getName());
+            switch (entity.getType()) {
+                case NotCompleted:
+                    notCompleted.put(entity.getJobId(), notification);
+                    break;
+                case NotStarted:
+                    notStarted.put(entity.getJobId(), notification);
+                    break;
+            }
+        }
+
+        ((DefaultUserTaskInstance) userTaskInstance).setNotStartedDeadlinesTimers(notStarted);
+        ((DefaultUserTaskInstance) userTaskInstance).setNotCompletedDeadlinesTimers(notCompleted);
+    }
+
+    private Notification readNotification(byte[] value) {
+        return (Notification) JSONUtils.stringTreeToValue(new String(value), Notification.class.getName());
+    }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java
index 12a6c50f98..a61ffabcdd 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java
@@ -32,7 +32,7 @@ import org.jbpm.usertask.jpa.repository.TaskInputRepository;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 
-public class TaskInputsEntityMapper {
+public class TaskInputsEntityMapper implements EntityMapper {
 
     private TaskInputRepository repository;
 
@@ -40,6 +40,7 @@ public class TaskInputsEntityMapper {
         this.repository = repository;
     }
 
+    @Override
     public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
         Collection<TaskInputEntity> toRemove = userTaskInstanceEntity.getInputs()
                 .stream()
@@ -66,6 +67,7 @@ public class TaskInputsEntityMapper {
         });
     }
 
+    @Override
     public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
         Map<String, Object> inputs = new HashMap<>();
         userTaskInstanceEntity.getInputs().forEach(taskInputEntity -> {
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java
index 999c735749..b4af4fc8a9 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java
@@ -31,7 +31,7 @@ import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 
-public class TaskMetadataEntityMapper {
+public class TaskMetadataEntityMapper implements EntityMapper {
 
     private final TaskMetadataRepository repository;
 
@@ -39,6 +39,7 @@ public class TaskMetadataEntityMapper {
         this.repository = repository;
     }
 
+    @Override
     public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
         Collection<TaskMetadataEntity> toRemove = userTaskInstanceEntity.getMetadata()
                 .stream()
@@ -64,6 +65,7 @@ public class TaskMetadataEntityMapper {
         });
     }
 
+    @Override
     public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
         Map<String, Object> metadata = new HashMap<>();
         userTaskInstanceEntity.getMetadata().forEach(metadataEntry -> {
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java
index 1fa7d4ae28..5f93dd53ee 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java
@@ -32,7 +32,7 @@ import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 
-public class TaskOutputsEntityMapper {
+public class TaskOutputsEntityMapper implements EntityMapper {
 
     private final TaskOutputRepository repository;
 
@@ -40,6 +40,7 @@ public class TaskOutputsEntityMapper {
         this.repository = repository;
     }
 
+    @Override
     public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
         Collection<TaskOutputEntity> toRemove = userTaskInstanceEntity.getOutputs()
                 .stream()
@@ -66,6 +67,7 @@ public class TaskOutputsEntityMapper {
         });
     }
 
+    @Override
     public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
         Map<String, Object> outputs = new HashMap<>();
         userTaskInstanceEntity.getOutputs().forEach(taskOutputEntity -> {
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentEntityMapper.java
new file mode 100644
index 0000000000..84a4f78308
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentEntityMapper.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskReassignmentEntity;
+import org.jbpm.usertask.jpa.model.TaskReassignmentType;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Reassignment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JavaType;
+
+public class TaskReassignmentEntityMapper implements EntityMapper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TaskReassignmentEntityMapper.class);
+
+    private final TaskReassignmentRepository repository;
+
+    public TaskReassignmentEntityMapper(TaskReassignmentRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+        if (userTaskInstanceEntity.getReassignments() == null) {
+            userTaskInstanceEntity.setReassignments(new ArrayList<>());
+        }
+        List<TaskReassignmentEntity> entities = new ArrayList<>();
+        List<DeadlineInfo<Reassignment>> notStartedReassignments = new ArrayList<>();
+        List<DeadlineInfo<Reassignment>> notCompletedReassignments = new ArrayList<>();
+        Iterator<TaskReassignmentEntity> iterator = userTaskInstanceEntity.getReassignments().iterator();
+        while (iterator.hasNext()) {
+            TaskReassignmentEntity reassignmentEntity = iterator.next();
+            DeadlineInfo<Reassignment> deadline = readNotification(reassignmentEntity.getValue());
+            switch (reassignmentEntity.getType()) {
+                case NotCompleted:
+                    if (!userTaskInstance.getNotCompletedReassignments().contains(deadline)) {
+                        entities.add(reassignmentEntity);
+                    } else {
+                        notCompletedReassignments.add(deadline);
+                    }
+                    break;
+                case NotStarted:
+                    if (!userTaskInstance.getNotStartedReassignments().contains(deadline)) {
+                        entities.add(reassignmentEntity);
+                    } else {
+                        notStartedReassignments.add(deadline);
+                    }
+                    break;
+            }
+        }
+
+        entities.forEach(e -> {
+            repository.remove(e);
+            userTaskInstanceEntity.getReassignments().remove(e);
+        });
+        entities.clear();
+
+        for (DeadlineInfo<Reassignment> reassignment : userTaskInstance.getNotStartedReassignments()) {
+            if (!notStartedReassignments.contains(reassignment)) {
+                entities.add(buildEntity(userTaskInstanceEntity, reassignment, TaskReassignmentType.NotStarted));
+            }
+        }
+
+        for (DeadlineInfo<Reassignment> reassignment : userTaskInstance.getNotCompletedReassignments()) {
+            if (!notCompletedReassignments.contains(reassignment)) {
+                entities.add(buildEntity(userTaskInstanceEntity, reassignment, TaskReassignmentType.NotCompleted));
+            }
+        }
+        entities.forEach(e -> {
+            userTaskInstanceEntity.getReassignments().add(e);
+        });
+    }
+
+    private TaskReassignmentEntity buildEntity(UserTaskInstanceEntity userTaskInstanceEntity, DeadlineInfo<Reassignment> deadline, TaskReassignmentType type) {
+        TaskReassignmentEntity entity = new TaskReassignmentEntity();
+        entity.setTaskInstance(userTaskInstanceEntity);
+        entity.setJavaType(DeadlineInfo.class.getName());
+        entity.setValue(JSONUtils.valueToString(deadline).getBytes(StandardCharsets.UTF_8));
+        entity.setType(type);
+        return entity;
+    }
+
+    private DeadlineInfo<Reassignment> readNotification(byte[] value) {
+        JavaType javaType = JSONUtils.buildJavaType(DeadlineInfo.class, Reassignment.class);
+        return (DeadlineInfo<Reassignment>) JSONUtils.stringTreeToValue(new String(value), javaType);
+    }
+
+    @Override
+    public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+        List<DeadlineInfo<Reassignment>> notStarted = new ArrayList<>();
+        List<DeadlineInfo<Reassignment>> notCompleted = new ArrayList<>();
+        JavaType javaType = JSONUtils.buildJavaType(DeadlineInfo.class, Reassignment.class);
+        for (TaskReassignmentEntity entity : userTaskInstanceEntity.getReassignments()) {
+            DeadlineInfo<Reassignment> deadline = (DeadlineInfo<Reassignment>) JSONUtils.stringTreeToValue(new String(entity.getValue()), javaType);
+            switch (entity.getType()) {
+                case NotCompleted:
+                    notCompleted.add(deadline);
+                    break;
+                case NotStarted:
+                    notStarted.add(deadline);
+                    break;
+            }
+        }
+
+        ((DefaultUserTaskInstance) userTaskInstance).setNotStartedReassignments(notStarted);
+        ((DefaultUserTaskInstance) userTaskInstance).setNotCompletedReassignments(notCompleted);
+    }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentTimerEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentTimerEntityMapper.java
new file mode 100644
index 0000000000..80366a07be
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskReassignmentTimerEntityMapper.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskReassignmentTimerEntity;
+import org.jbpm.usertask.jpa.model.TaskReassignmentType;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentTimerRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.Reassignment;
+
+public class TaskReassignmentTimerEntityMapper implements EntityMapper {
+
+    private final TaskReassignmentTimerRepository repository;
+
+    public TaskReassignmentTimerEntityMapper(TaskReassignmentTimerRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public void mapInstanceToEntity(UserTaskInstance instance, UserTaskInstanceEntity userTaskInstanceEntity) {
+        if (userTaskInstanceEntity.getReassignmentTimers() == null) {
+            userTaskInstanceEntity.setReassignmentTimers(new ArrayList<>());
+        }
+        DefaultUserTaskInstance userTaskInstance = (DefaultUserTaskInstance) instance;
+
+        List<TaskReassignmentTimerEntity> entities = new ArrayList<>();
+        List<String> notStartedReassignments = new ArrayList<>();
+        List<String> notCompletedReassignments = new ArrayList<>();
+
+        Iterator<TaskReassignmentTimerEntity> iterator = userTaskInstanceEntity.getReassignmentTimers().iterator();
+        while (iterator.hasNext()) {
+            TaskReassignmentTimerEntity deadlineEntity = iterator.next();
+            switch (deadlineEntity.getType()) {
+                case NotCompleted:
+                    if (!userTaskInstance.getNotCompletedReassignmentsTimers().keySet().contains(deadlineEntity.getJobId())) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notCompletedReassignments.add(deadlineEntity.getJobId());
+                    }
+                    break;
+                case NotStarted:
+                    if (!userTaskInstance.getNotStartedReassignmentsTimers().keySet().contains(deadlineEntity.getJobId())) {
+                        entities.add(deadlineEntity);
+                    } else {
+                        notStartedReassignments.add(deadlineEntity.getJobId());
+                    }
+                    break;
+            }
+        }
+
+        entities.forEach(e -> {
+            repository.remove(e);
+            userTaskInstanceEntity.getReassignmentTimers().remove(e);
+        });
+        entities.clear();
+
+        for (Map.Entry<String, Reassignment> timer : userTaskInstance.getNotStartedReassignmentsTimers().entrySet()) {
+            if (!notStartedReassignments.contains(timer.getKey())) {
+                TaskReassignmentTimerEntity entity = buildEntity(userTaskInstanceEntity, timer.getKey(), timer.getValue(), TaskReassignmentType.NotStarted);
+                entities.add(entity);
+            }
+        }
+
+        for (Map.Entry<String, Reassignment> timer : userTaskInstance.getNotCompletedReassignmentsTimers().entrySet()) {
+            if (!notCompletedReassignments.contains(timer.getKey())) {
+                TaskReassignmentTimerEntity entity = buildEntity(userTaskInstanceEntity, timer.getKey(), timer.getValue(), TaskReassignmentType.NotCompleted);
+                entities.add(entity);
+            }
+        }
+        entities.forEach(userTaskInstanceEntity.getReassignmentTimers()::add);
+    }
+
+    private TaskReassignmentTimerEntity buildEntity(UserTaskInstanceEntity userTaskInstanceEntity, String jobId, Reassignment reassignment, TaskReassignmentType type) {
+        TaskReassignmentTimerEntity entity = new TaskReassignmentTimerEntity();
+        entity.setJobId(jobId);
+        entity.setTaskInstance(userTaskInstanceEntity);
+        entity.setJavaType(Reassignment.class.getName());
+        entity.setValue(JSONUtils.valueToString(reassignment).getBytes(StandardCharsets.UTF_8));
+        entity.setType(type);
+        return entity;
+    }
+
+    @Override
+    public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+        Map<String, Reassignment> notStarted = new HashMap<>();
+        Map<String, Reassignment> notCompleted = new HashMap<>();
+        for (TaskReassignmentTimerEntity entity : userTaskInstanceEntity.getReassignmentTimers()) {
+            Reassignment notification = (Reassignment) JSONUtils.stringTreeToValue(new String(entity.getValue()), Reassignment.class.getName());
+            switch (entity.getType()) {
+                case NotCompleted:
+                    notCompleted.put(entity.getJobId(), notification);
+                    break;
+                case NotStarted:
+                    notStarted.put(entity.getJobId(), notification);
+                    break;
+            }
+        }
+
+        ((DefaultUserTaskInstance) userTaskInstance).setNotStartedReassignmentsTimers(notStarted);
+        ((DefaultUserTaskInstance) userTaskInstance).setNotCompletedReassignmentsTimers(notCompleted);
+    }
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java
index e03125742b..c183ad0f22 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java
@@ -19,6 +19,8 @@
 
 package org.jbpm.usertask.jpa.mapper;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
@@ -28,19 +30,11 @@ import org.kie.kogito.usertask.lifecycle.UserTaskState;
 
 public class UserTaskInstanceEntityMapper {
 
-    private final AttachmentsEntityMapper attachmentsMapper;
-    private final CommentsEntityMapper commentMapper;
-    private final TaskInputsEntityMapper taskInputsMapper;
-    private final TaskOutputsEntityMapper taskOutputsMapper;
-    private final TaskMetadataEntityMapper taskMetadataMapper;
-
-    public UserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataMapper,
-            TaskInputsEntityMapper taskInputsMapper, TaskOutputsEntityMapper taskOutputMapper) {
-        this.attachmentsMapper = attachmentsMapper;
-        this.commentMapper = commentsMapper;
-        this.taskMetadataMapper = taskMetadataMapper;
-        this.taskInputsMapper = taskInputsMapper;
-        this.taskOutputsMapper = taskOutputMapper;
+    private List<EntityMapper> mappers;
+
+    public UserTaskInstanceEntityMapper(Iterable<EntityMapper> mappers) {
+        this.mappers = new ArrayList<>();
+        mappers.forEach(this.mappers::add);
     }
 
     public UserTaskInstanceEntity mapTaskInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity entity) {
@@ -60,11 +54,7 @@ public class UserTaskInstanceEntityMapper {
         entity.setAdminGroups(Set.copyOf(userTaskInstance.getAdminGroups()));
         entity.setExcludedUsers(Set.copyOf(userTaskInstance.getExcludedUsers()));
 
-        attachmentsMapper.mapInstanceToEntity(userTaskInstance, entity);
-        commentMapper.mapInstanceToEntity(userTaskInstance, entity);
-        taskInputsMapper.mapInstanceToEntity(userTaskInstance, entity);
-        taskOutputsMapper.mapInstanceToEntity(userTaskInstance, entity);
-        taskMetadataMapper.mapInstanceToEntity(userTaskInstance, entity);
+        mappers.forEach(e -> e.mapInstanceToEntity(userTaskInstance, entity));
 
         return entity;
     }
@@ -90,11 +80,7 @@ public class UserTaskInstanceEntityMapper {
         instance.setAdminGroups(Set.copyOf(entity.getAdminGroups()));
         instance.setExcludedUsers(Set.copyOf(entity.getExcludedUsers()));
 
-        attachmentsMapper.mapEntityToInstance(entity, instance);
-        commentMapper.mapEntityToInstance(entity, instance);
-        taskInputsMapper.mapEntityToInstance(entity, instance);
-        taskOutputsMapper.mapEntityToInstance(entity, instance);
-        taskMetadataMapper.mapEntityToInstance(entity, instance);
+        mappers.forEach(e -> e.mapEntityToInstance(entity, instance));
 
         return instance;
     }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java
index b7e3358980..ab4d13f49d 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java
@@ -22,9 +22,12 @@ package org.jbpm.usertask.jpa.mapper.json.utils;
 import java.util.Objects;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
+import static java.lang.Thread.currentThread;
+
 public class JSONUtils {
 
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -33,6 +36,10 @@ public class JSONUtils {
         OBJECT_MAPPER.registerModule(new JavaTimeModule());
     }
 
+    public static JavaType buildJavaType(Class<?> clazz, Class<?> parameter) {
+        return OBJECT_MAPPER.getTypeFactory().constructParametricType(clazz, parameter);
+    }
+
     public static String valueToString(Object value) {
         try {
             return OBJECT_MAPPER.writeValueAsString(value);
@@ -46,7 +53,18 @@ public class JSONUtils {
             if (Objects.isNull(value) || Objects.isNull(javaType)) {
                 return null;
             }
-            return OBJECT_MAPPER.readValue(value, Class.forName(javaType));
+            return OBJECT_MAPPER.readValue(value, currentThread().getContextClassLoader().loadClass(javaType));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Object stringTreeToValue(String value, JavaType javaType) {
+        try {
+            if (Objects.isNull(value) || Objects.isNull(javaType)) {
+                return null;
+            }
+            return OBJECT_MAPPER.readValue(value, javaType);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AbstractTaskEntity.java
similarity index 60%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AbstractTaskEntity.java
index 506f3bf925..9a1fed04b4 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AbstractTaskEntity.java
@@ -21,19 +21,11 @@ package org.jbpm.usertask.jpa.model;
 
 import java.util.Objects;
 
-import jakarta.persistence.*;
+import jakarta.persistence.Column;
+import jakarta.persistence.MappedSuperclass;
 
 @MappedSuperclass
-public abstract class TaskDataEntity<T> {
-
-    @Id
-    @Column(name = "name")
-    protected String name;
-
-    @Id
-    @ManyToOne(optional = false)
-    @JoinColumn(name = "task_id")
-    protected UserTaskInstanceEntity taskInstance;
+public abstract class AbstractTaskEntity<T> {
 
     @Column(name = "value")
     protected T value;
@@ -41,22 +33,6 @@ public abstract class TaskDataEntity<T> {
     @Column(name = "java_type")
     protected String javaType;
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public UserTaskInstanceEntity getTaskInstance() {
-        return taskInstance;
-    }
-
-    public void setTaskInstance(UserTaskInstanceEntity taskInstance) {
-        this.taskInstance = taskInstance;
-    }
-
     public T getValue() {
         return value;
     }
@@ -79,13 +55,12 @@ public abstract class TaskDataEntity<T> {
             return true;
         if (o == null || getClass() != o.getClass())
             return false;
-        TaskDataEntity<?> that = (TaskDataEntity<?>) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance()) && Objects.equals(getValue(),
-                that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
+        AbstractTaskEntity<?> that = (AbstractTaskEntity<?>) o;
+        return Objects.equals(getValue(), that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(getName(), getTaskInstance(), getValue(), getJavaType());
+        return Objects.hash(getValue(), getJavaType());
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineEntity.java
new file mode 100644
index 0000000000..35f790adb1
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineEntity.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import java.util.Objects;
+
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "jbpm_user_tasks_deadline")
+@AttributeOverrides({
+        @AttributeOverride(name = "value", column = @Column(name = "notification_value"))
+})
+@AssociationOverride(name = "taskInstance",
+        joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_deadline_tid")))
+public class TaskDeadlineEntity extends TaskTimerConfigEntity<byte[]> {
+
+    @Column(name = "notification_type")
+    @Enumerated(EnumType.STRING)
+    private TaskDeadlineType type;
+
+    public TaskDeadlineType getType() {
+        return type;
+    }
+
+    public void setType(TaskDeadlineType type) {
+        this.type = type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value, javaType, type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TaskDeadlineEntity other = (TaskDeadlineEntity) obj;
+        return type == other.type && super.equals(obj);
+    }
+
+    @Override
+    public String toString() {
+        return "TaskDeadlineEntity [type=" + type + ", id=" + id + ", taskInstance=" + taskInstance.getId() + ", javaType=" + javaType + "]";
+    }
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineTimerEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineTimerEntity.java
new file mode 100644
index 0000000000..0c3d762f22
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineTimerEntity.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import java.util.Objects;
+
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "jbpm_user_tasks_deadline_timer")
+@AttributeOverrides({
+        @AttributeOverride(name = "jobId", column = @Column(name = "notification_job_id")),
+        @AttributeOverride(name = "value", column = @Column(name = "notification_value"))
+})
+@AssociationOverride(name = "taskInstance",
+        joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_deadline_timer_tid")))
+public class TaskDeadlineTimerEntity extends TaskTimerDataEntity<byte[]> {
+
+    @Column(name = "notification_type")
+    @Enumerated(EnumType.STRING)
+    private TaskDeadlineType type;
+
+    public TaskDeadlineType getType() {
+        return type;
+    }
+
+    public void setType(TaskDeadlineType type) {
+        this.type = type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getJobId(), getValue(), getJavaType(), type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TaskDeadlineTimerEntity other = (TaskDeadlineTimerEntity) obj;
+        return super.equals(obj) && type == other.type;
+    }
+
+}
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineType.java
similarity index 84%
copy from api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineType.java
index 5bbd520bfc..f388f935d6 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDeadlineType.java
@@ -16,13 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.jbpm.usertask.jpa.model;
 
-public interface JobDescription {
-
-    String id();
-
-    ExpirationTime expirationTime();
-
-    Integer priority();
+public enum TaskDeadlineType {
+    NotStarted,
+    NotCompleted
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java
index 9c4351cfd2..3439ed45fd 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java
@@ -19,7 +19,13 @@
 
 package org.jbpm.usertask.jpa.model;
 
-import jakarta.persistence.*;
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.Table;
 
 @Entity
 @Table(name = "jbpm_user_tasks_inputs")
@@ -28,7 +34,6 @@ import jakarta.persistence.*;
         @AttributeOverride(name = "value", column = @Column(name = "input_value"))
 })
 @AssociationOverride(name = "taskInstance", foreignKey = @ForeignKey(name = "jbpm_user_tasks_inputs_tid"))
-@IdClass(TaskDataEntityPK.class)
-public class TaskInputEntity extends TaskDataEntity<byte[]> {
+public class TaskInputEntity extends TaskNamedDataEntity<byte[]> {
 
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java
index 2729ee6871..b7d05701c8 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java
@@ -19,7 +19,14 @@
 
 package org.jbpm.usertask.jpa.model;
 
-import jakarta.persistence.*;
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
 
 @Entity
 @Table(name = "jbpm_user_tasks_metadata")
@@ -29,7 +36,6 @@ import jakarta.persistence.*;
 })
 @AssociationOverride(name = "taskInstance",
         joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_metadata_tid")))
-@IdClass(TaskDataEntityPK.class)
-public class TaskMetadataEntity extends TaskDataEntity<String> {
+public class TaskMetadataEntity extends TaskNamedDataEntity<String> {
 
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntity.java
similarity index 66%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntity.java
index 506f3bf925..45b1c9f601 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntity.java
@@ -21,25 +21,25 @@ package org.jbpm.usertask.jpa.model;
 
 import java.util.Objects;
 
-import jakarta.persistence.*;
+import jakarta.persistence.Column;
+import jakarta.persistence.Id;
+import jakarta.persistence.IdClass;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.MappedSuperclass;
 
 @MappedSuperclass
-public abstract class TaskDataEntity<T> {
-
-    @Id
-    @Column(name = "name")
-    protected String name;
+@IdClass(TaskNamedDataEntityPK.class)
+public abstract class TaskNamedDataEntity<T> extends AbstractTaskEntity<T> {
 
     @Id
     @ManyToOne(optional = false)
     @JoinColumn(name = "task_id")
     protected UserTaskInstanceEntity taskInstance;
 
-    @Column(name = "value")
-    protected T value;
-
-    @Column(name = "java_type")
-    protected String javaType;
+    @Id
+    @Column(name = "name")
+    protected String name;
 
     public String getName() {
         return name;
@@ -57,35 +57,20 @@ public abstract class TaskDataEntity<T> {
         this.taskInstance = taskInstance;
     }
 
-    public T getValue() {
-        return value;
-    }
-
-    public void setValue(T value) {
-        this.value = value;
-    }
-
-    public String getJavaType() {
-        return javaType;
-    }
-
-    public void setJavaType(String javaType) {
-        this.javaType = javaType;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (this == o)
             return true;
         if (o == null || getClass() != o.getClass())
             return false;
-        TaskDataEntity<?> that = (TaskDataEntity<?>) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance()) && Objects.equals(getValue(),
-                that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
+        TaskNamedDataEntity<?> that = (TaskNamedDataEntity<?>) o;
+        return Objects.equals(getName(), that.getName()) &&
+                Objects.equals(getValue(), that.getValue()) &&
+                Objects.equals(getJavaType(), that.getJavaType());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(getName(), getTaskInstance(), getValue(), getJavaType());
+        return Objects.hash(getName(), getValue(), getJavaType());
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntityPK.java
similarity index 78%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntityPK.java
index 4d0f1fab9e..1c8c4f5ce5 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskNamedDataEntityPK.java
@@ -22,15 +22,17 @@ package org.jbpm.usertask.jpa.model;
 import java.io.Serializable;
 import java.util.Objects;
 
-public class TaskDataEntityPK implements Serializable {
+public class TaskNamedDataEntityPK implements Serializable {
+
+    private static final long serialVersionUID = 5506586793841760884L;
 
     private String name;
     private UserTaskInstanceEntity taskInstance;
 
-    public TaskDataEntityPK() {
+    public TaskNamedDataEntityPK() {
     }
 
-    public TaskDataEntityPK(String inputName, UserTaskInstanceEntity taskInstance) {
+    public TaskNamedDataEntityPK(String inputName, UserTaskInstanceEntity taskInstance) {
         this.taskInstance = taskInstance;
         this.name = inputName;
     }
@@ -57,8 +59,8 @@ public class TaskDataEntityPK implements Serializable {
             return true;
         if (o == null || getClass() != o.getClass())
             return false;
-        TaskDataEntityPK that = (TaskDataEntityPK) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance());
+        TaskNamedDataEntityPK that = (TaskNamedDataEntityPK) o;
+        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance().getId(), that.getTaskInstance().getId());
     }
 
     @Override
@@ -68,8 +70,8 @@ public class TaskDataEntityPK implements Serializable {
 
     @Override
     public String toString() {
-        return "TaskInputEntityId{" +
-                "taskInstance='" + taskInstance + '\'' +
+        return "TaskNamedDataEntityId {" +
+                "taskInstance='" + taskInstance.getId() + '\'' +
                 ", name='" + name + '\'' +
                 '}';
     }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java
index bd7098f245..7961dcecc9 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java
@@ -19,7 +19,14 @@
 
 package org.jbpm.usertask.jpa.model;
 
-import jakarta.persistence.*;
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
 
 @Entity
 @Table(name = "jbpm_user_tasks_outputs")
@@ -29,7 +36,6 @@ import jakarta.persistence.*;
 })
 @AssociationOverride(name = "taskInstance",
         joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_outputs_tid")))
-@IdClass(TaskDataEntityPK.class)
-public class TaskOutputEntity extends TaskDataEntity<byte[]> {
+public class TaskOutputEntity extends TaskNamedDataEntity<byte[]> {
 
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentEntity.java
new file mode 100644
index 0000000000..9bd409cd7d
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentEntity.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import java.util.Objects;
+
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "jbpm_user_tasks_reassignment")
+@AttributeOverrides({
+        @AttributeOverride(name = "value", column = @Column(name = "reassignment_value"))
+})
+@AssociationOverride(name = "taskInstance",
+        joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_reassignment_tid")))
+public class TaskReassignmentEntity extends TaskTimerConfigEntity<byte[]> {
+
+    @Column(name = "reassignment_type")
+    @Enumerated(EnumType.STRING)
+    private TaskReassignmentType type;
+
+    public TaskReassignmentType getType() {
+        return type;
+    }
+
+    public void setType(TaskReassignmentType type) {
+        this.type = type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value, javaType, type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TaskReassignmentEntity other = (TaskReassignmentEntity) obj;
+        return type == other.type && super.equals(obj);
+    }
+
+    @Override
+    public String toString() {
+        return "TaskReassignmentEntity [type=" + type + ", id=" + id + ", taskInstance=" + taskInstance.getId() + ", javaType=" + javaType + "]";
+    }
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentTimerEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentTimerEntity.java
new file mode 100644
index 0000000000..8b735917c5
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentTimerEntity.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import java.util.Objects;
+
+import jakarta.persistence.AssociationOverride;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "jbpm_user_tasks_reassignment_timer")
+@AttributeOverrides({
+        @AttributeOverride(name = "jobId", column = @Column(name = "reassignment_job_id")),
+        @AttributeOverride(name = "value", column = @Column(name = "reassignment_value"))
+})
+@AssociationOverride(name = "taskInstance",
+        joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_reassignment_timer_tid")))
+public class TaskReassignmentTimerEntity extends TaskTimerDataEntity<byte[]> {
+
+    @Column(name = "reassignment_type")
+    @Enumerated(EnumType.STRING)
+    private TaskReassignmentType type;
+
+    public TaskReassignmentType getType() {
+        return type;
+    }
+
+    public void setType(TaskReassignmentType type) {
+        this.type = type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getJobId(), getValue(), getJavaType(), type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TaskReassignmentTimerEntity other = (TaskReassignmentTimerEntity) obj;
+        return super.equals(obj) && type == other.type;
+    }
+}
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentType.java
similarity index 84%
copy from api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentType.java
index 5bbd520bfc..bf4e2a8da2 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskReassignmentType.java
@@ -16,13 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.jbpm.usertask.jpa.model;
 
-public interface JobDescription {
-
-    String id();
-
-    ExpirationTime expirationTime();
-
-    Integer priority();
+public enum TaskReassignmentType {
+    NotStarted,
+    NotCompleted
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
similarity index 54%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
index 506f3bf925..1f59855a80 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
@@ -21,32 +21,30 @@ package org.jbpm.usertask.jpa.model;
 
 import java.util.Objects;
 
-import jakarta.persistence.*;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.MappedSuperclass;
 
 @MappedSuperclass
-public abstract class TaskDataEntity<T> {
+public abstract class TaskTimerConfigEntity<T> extends AbstractTaskEntity<T> {
 
     @Id
-    @Column(name = "name")
-    protected String name;
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    protected Integer id;
 
-    @Id
     @ManyToOne(optional = false)
     @JoinColumn(name = "task_id")
     protected UserTaskInstanceEntity taskInstance;
 
-    @Column(name = "value")
-    protected T value;
-
-    @Column(name = "java_type")
-    protected String javaType;
-
-    public String getName() {
-        return name;
+    public Integer getId() {
+        return id;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public void setId(Integer id) {
+        this.id = id;
     }
 
     public UserTaskInstanceEntity getTaskInstance() {
@@ -57,35 +55,21 @@ public abstract class TaskDataEntity<T> {
         this.taskInstance = taskInstance;
     }
 
-    public T getValue() {
-        return value;
-    }
-
-    public void setValue(T value) {
-        this.value = value;
-    }
-
-    public String getJavaType() {
-        return javaType;
-    }
-
-    public void setJavaType(String javaType) {
-        this.javaType = javaType;
+    @Override
+    public int hashCode() {
+        return Objects.hash(value, javaType, id, taskInstance.getId());
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o)
+    public boolean equals(Object obj) {
+        if (this == obj)
             return true;
-        if (o == null || getClass() != o.getClass())
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
             return false;
-        TaskDataEntity<?> that = (TaskDataEntity<?>) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance()) && Objects.equals(getValue(),
-                that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
+        TaskTimerConfigEntity other = (TaskTimerConfigEntity) obj;
+        return super.equals(obj) && Objects.equals(id, other.id) && Objects.equals(taskInstance.getId(), other.taskInstance.getId());
     }
 
-    @Override
-    public int hashCode() {
-        return Objects.hash(getName(), getTaskInstance(), getValue(), getJavaType());
-    }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntity.java
similarity index 59%
rename from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
rename to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntity.java
index 506f3bf925..67b7e21729 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntity.java
@@ -21,32 +21,32 @@ package org.jbpm.usertask.jpa.model;
 
 import java.util.Objects;
 
-import jakarta.persistence.*;
+import jakarta.persistence.Column;
+import jakarta.persistence.Id;
+import jakarta.persistence.IdClass;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.MappedSuperclass;
 
 @MappedSuperclass
-public abstract class TaskDataEntity<T> {
-
-    @Id
-    @Column(name = "name")
-    protected String name;
+@IdClass(TaskTimerDataEntityPK.class)
+public abstract class TaskTimerDataEntity<T> extends AbstractTaskEntity<T> {
 
     @Id
     @ManyToOne(optional = false)
     @JoinColumn(name = "task_id")
     protected UserTaskInstanceEntity taskInstance;
 
-    @Column(name = "value")
-    protected T value;
-
-    @Column(name = "java_type")
-    protected String javaType;
+    @Id
+    @Column(name = "jobId")
+    protected String jobId;
 
-    public String getName() {
-        return name;
+    public String getJobId() {
+        return jobId;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public void setJobId(String jobId) {
+        this.jobId = jobId;
     }
 
     public UserTaskInstanceEntity getTaskInstance() {
@@ -57,35 +57,18 @@ public abstract class TaskDataEntity<T> {
         this.taskInstance = taskInstance;
     }
 
-    public T getValue() {
-        return value;
-    }
-
-    public void setValue(T value) {
-        this.value = value;
-    }
-
-    public String getJavaType() {
-        return javaType;
-    }
-
-    public void setJavaType(String javaType) {
-        this.javaType = javaType;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (this == o)
             return true;
         if (o == null || getClass() != o.getClass())
             return false;
-        TaskDataEntity<?> that = (TaskDataEntity<?>) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance()) && Objects.equals(getValue(),
-                that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
+        TaskTimerDataEntity<?> that = (TaskTimerDataEntity<?>) o;
+        return Objects.equals(getJobId(), that.getJobId()) && super.equals(o);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(getName(), getTaskInstance(), getValue(), getJavaType());
+        return Objects.hash(getJobId(), getValue(), getJavaType());
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntityPK.java
similarity index 64%
rename from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
rename to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntityPK.java
index 4d0f1fab9e..d4c1c06ea6 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerDataEntityPK.java
@@ -22,17 +22,19 @@ package org.jbpm.usertask.jpa.model;
 import java.io.Serializable;
 import java.util.Objects;
 
-public class TaskDataEntityPK implements Serializable {
+public class TaskTimerDataEntityPK implements Serializable {
 
-    private String name;
+    private static final long serialVersionUID = 5506586793841760884L;
+
+    private String jobId;
     private UserTaskInstanceEntity taskInstance;
 
-    public TaskDataEntityPK() {
+    public TaskTimerDataEntityPK() {
     }
 
-    public TaskDataEntityPK(String inputName, UserTaskInstanceEntity taskInstance) {
+    public TaskTimerDataEntityPK(String jobId, UserTaskInstanceEntity taskInstance) {
         this.taskInstance = taskInstance;
-        this.name = inputName;
+        this.jobId = jobId;
     }
 
     public UserTaskInstanceEntity getTaskInstance() {
@@ -43,12 +45,12 @@ public class TaskDataEntityPK implements Serializable {
         this.taskInstance = taskInstance;
     }
 
-    public String getName() {
-        return name;
+    public String getJobId() {
+        return jobId;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public void setJobId(String jobId) {
+        this.jobId = jobId;
     }
 
     @Override
@@ -57,20 +59,20 @@ public class TaskDataEntityPK implements Serializable {
             return true;
         if (o == null || getClass() != o.getClass())
             return false;
-        TaskDataEntityPK that = (TaskDataEntityPK) o;
-        return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance());
+        TaskTimerDataEntityPK that = (TaskTimerDataEntityPK) o;
+        return Objects.equals(getJobId(), that.getJobId()) && Objects.equals(getTaskInstance().getId(), that.getTaskInstance().getId());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(getName(), getTaskInstance());
+        return Objects.hash(getJobId(), getTaskInstance());
     }
 
     @Override
     public String toString() {
-        return "TaskInputEntityId{" +
-                "taskInstance='" + taskInstance + '\'' +
-                ", name='" + name + '\'' +
+        return "TaskTimerDataEntityId {" +
+                "taskInstance='" + taskInstance.getId() + '\'' +
+                ", name='" + jobId + '\'' +
                 '}';
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
index 8b3cfca028..b721f0ce3f 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
@@ -19,9 +19,25 @@
 
 package org.jbpm.usertask.jpa.model;
 
-import java.util.*;
-
-import jakarta.persistence.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.CollectionTable;
+import jakarta.persistence.Column;
+import jakarta.persistence.ElementCollection;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.ForeignKey;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
 
 @Entity
 @NamedQuery(name = UserTaskInstanceEntity.GET_INSTANCES_BY_IDENTITY,
@@ -105,6 +121,18 @@ public class UserTaskInstanceEntity {
     @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
     private List<TaskMetadataEntity> metadata = new ArrayList<>();
 
+    @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private List<TaskDeadlineEntity> deadlines;
+
+    @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private List<TaskDeadlineTimerEntity> deadlineTimers;
+
+    @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private List<TaskReassignmentEntity> reassignments;
+
+    @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private List<TaskReassignmentTimerEntity> reassignmentTimers;
+
     public String getId() {
         return id;
     }
@@ -331,4 +359,62 @@ public class UserTaskInstanceEntity {
     public String getTerminationType() {
         return terminationType;
     }
+
+    public List<TaskDeadlineEntity> getDeadlines() {
+        return deadlines;
+    }
+
+    public void setDeadlines(List<TaskDeadlineEntity> deadlines) {
+        this.deadlines = deadlines;
+    }
+
+    public List<TaskReassignmentEntity> getReassignments() {
+        return reassignments;
+    }
+
+    public void setReassignments(List<TaskReassignmentEntity> reassignments) {
+        this.reassignments = reassignments;
+    }
+
+    public List<TaskDeadlineTimerEntity> getDeadlineTimers() {
+        return deadlineTimers;
+    }
+
+    public void setDeadlineTimers(List<TaskDeadlineTimerEntity> deadlineTimers) {
+        this.deadlineTimers = deadlineTimers;
+    }
+
+    public List<TaskReassignmentTimerEntity> getReassignmentTimers() {
+        return reassignmentTimers;
+    }
+
+    public void setReassignmentTimers(List<TaskReassignmentTimerEntity> reassignmentTimers) {
+        this.reassignmentTimers = reassignmentTimers;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(actualOwner, adminGroups, adminUsers, attachments, comments, deadlineTimers, deadlines, excludedUsers, externalReferenceId, id, inputs, metadata, outputs, potentialGroups,
+                potentialUsers, reassignmentTimers, reassignments, status, taskDescription, taskName, taskPriority, terminationType, userTaskId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        UserTaskInstanceEntity other = (UserTaskInstanceEntity) obj;
+        return Objects.equals(actualOwner, other.actualOwner) && Objects.equals(adminGroups, other.adminGroups) && Objects.equals(adminUsers, other.adminUsers)
+                && Objects.equals(attachments, other.attachments) && Objects.equals(comments, other.comments) && Objects.equals(deadlineTimers, other.deadlineTimers)
+                && Objects.equals(deadlines, other.deadlines) && Objects.equals(excludedUsers, other.excludedUsers) && Objects.equals(externalReferenceId, other.externalReferenceId)
+                && Objects.equals(id, other.id) && Objects.equals(inputs, other.inputs) && Objects.equals(metadata, other.metadata) && Objects.equals(outputs, other.outputs)
+                && Objects.equals(potentialGroups, other.potentialGroups) && Objects.equals(potentialUsers, other.potentialUsers) && Objects.equals(reassignmentTimers, other.reassignmentTimers)
+                && Objects.equals(reassignments, other.reassignments) && Objects.equals(status, other.status) && Objects.equals(taskDescription, other.taskDescription)
+                && Objects.equals(taskName, other.taskName) && Objects.equals(taskPriority, other.taskPriority) && Objects.equals(terminationType, other.terminationType)
+                && Objects.equals(userTaskId, other.userTaskId);
+    }
+
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineRepository.java
similarity index 72%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineRepository.java
index 6e85081ff7..2fc7cedc95 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineRepository.java
@@ -19,17 +19,16 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.TaskDeadlineEntity;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public class TaskDeadlineRepository extends BaseRepository<TaskDeadlineEntity, Integer> {
 
-    public TaskInputRepository(UserTaskJPAContext context) {
+    public TaskDeadlineRepository(UserTaskJPAContext context) {
         super(context);
     }
 
     @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    public Class<TaskDeadlineEntity> getEntityClass() {
+        return TaskDeadlineEntity.class;
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineTimerRepository.java
similarity index 68%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineTimerRepository.java
index 6e85081ff7..370f9e3591 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskDeadlineTimerRepository.java
@@ -19,17 +19,17 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.TaskDeadlineTimerEntity;
+import org.jbpm.usertask.jpa.model.TaskTimerDataEntityPK;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public class TaskDeadlineTimerRepository extends BaseRepository<TaskDeadlineTimerEntity, TaskTimerDataEntityPK> {
 
-    public TaskInputRepository(UserTaskJPAContext context) {
+    public TaskDeadlineTimerRepository(UserTaskJPAContext context) {
         super(context);
     }
 
     @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    public Class<TaskDeadlineTimerEntity> getEntityClass() {
+        return TaskDeadlineTimerEntity.class;
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
index 6e85081ff7..344fbba90a 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
@@ -19,10 +19,10 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
 import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.TaskNamedDataEntityPK;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskNamedDataEntityPK> {
 
     public TaskInputRepository(UserTaskJPAContext context) {
         super(context);
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java
index 26796bb38a..836fa5dd07 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java
@@ -19,10 +19,10 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
 import org.jbpm.usertask.jpa.model.TaskMetadataEntity;
+import org.jbpm.usertask.jpa.model.TaskNamedDataEntityPK;
 
-public class TaskMetadataRepository extends BaseRepository<TaskMetadataEntity, TaskDataEntityPK> {
+public class TaskMetadataRepository extends BaseRepository<TaskMetadataEntity, TaskNamedDataEntityPK> {
 
     public TaskMetadataRepository(UserTaskJPAContext context) {
         super(context);
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java
index 2b39b8f1bb..610cde606d 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java
@@ -19,10 +19,10 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
+import org.jbpm.usertask.jpa.model.TaskNamedDataEntityPK;
 import org.jbpm.usertask.jpa.model.TaskOutputEntity;
 
-public class TaskOutputRepository extends BaseRepository<TaskOutputEntity, TaskDataEntityPK> {
+public class TaskOutputRepository extends BaseRepository<TaskOutputEntity, TaskNamedDataEntityPK> {
 
     public TaskOutputRepository(UserTaskJPAContext context) {
         super(context);
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentRepository.java
similarity index 72%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentRepository.java
index 6e85081ff7..ae01f0fd44 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentRepository.java
@@ -19,17 +19,16 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.TaskReassignmentEntity;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public class TaskReassignmentRepository extends BaseRepository<TaskReassignmentEntity, Integer> {
 
-    public TaskInputRepository(UserTaskJPAContext context) {
+    public TaskReassignmentRepository(UserTaskJPAContext context) {
         super(context);
     }
 
     @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    public Class<TaskReassignmentEntity> getEntityClass() {
+        return TaskReassignmentEntity.class;
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentTimerRepository.java
similarity index 67%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentTimerRepository.java
index 6e85081ff7..f9124609db 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskReassignmentTimerRepository.java
@@ -19,17 +19,17 @@
 
 package org.jbpm.usertask.jpa.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.TaskReassignmentTimerEntity;
+import org.jbpm.usertask.jpa.model.TaskTimerDataEntityPK;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public class TaskReassignmentTimerRepository extends BaseRepository<TaskReassignmentTimerEntity, TaskTimerDataEntityPK> {
 
-    public TaskInputRepository(UserTaskJPAContext context) {
+    public TaskReassignmentTimerRepository(UserTaskJPAContext context) {
         super(context);
     }
 
     @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    public Class<TaskReassignmentTimerEntity> getEntityClass() {
+        return TaskReassignmentTimerEntity.class;
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.1__jBPM_user_task_timers.sql b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.1__jBPM_user_task_timers.sql
new file mode 100644
index 0000000000..80154790b0
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.1__jBPM_user_task_timers.sql
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+create table jbpm_user_tasks_deadline (
+    id                  int,
+    task_id             varchar(50) not null,
+    notification_type   varchar(255) not null,
+    notification_value  varbinary(max),
+    java_type           varchar(255),
+    primary key (id)
+);
+
+
+create table jbpm_user_tasks_reassignment (
+    id                  int,
+    task_id             varchar(50) not null,
+    reassignment_type   varchar(255) not null,
+    reassignment_value  varbinary(max),
+    java_type           varchar(255),
+    primary key (id)
+);
+
+
+create table jbpm_user_tasks_deadline_timer (
+    task_id             varchar(50) not null,
+    notification_job_id varchar(255) not null,
+    notification_type   varchar(255) not null,
+    notification_value  varbinary(max),
+    java_type           varchar(255),
+    primary key (task_id, notification_job_id)
+);
+
+create table jbpm_user_tasks_reassignment_timer (
+    task_id             varchar(50) not null,
+    reassignment_job_id varchar(255) not null,
+    reassignment_type   varchar(255) not null,
+    reassignment_value  varbinary(max),
+    java_type           varchar(255),
+    primary key (task_id, reassignment_job_id)
+);
+
+CREATE SEQUENCE jbpm_user_tasks_deadline_seq INCREMENT BY 50;
+CREATE SEQUENCE jbpm_user_tasks_reassignment_seq INCREMENT BY 50;
+
+alter table if exists jbpm_user_tasks_deadline
+add constraint fk_jbpm_user_tasks_deadline_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_reassignment
+add constraint fk_jbpm_user_tasks_reassignment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_deadline_timer
+add constraint fk_jbpm_user_tasks_deadline_timer_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+
+alter table if exists jbpm_user_tasks_reassignment_timer
+add constraint fk_jbpm_user_tasks_reassignment_timer_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.1__jBPM_user_task_timers.sql b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.1__jBPM_user_task_timers.sql
new file mode 100644
index 0000000000..e54054517d
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.1__jBPM_user_task_timers.sql
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+create table jbpm_user_tasks_deadline (
+    id                  int,
+    task_id             varchar(50) not null,
+    notification_type   varchar(255) not null,
+    notification_value  bytea,
+    java_type           varchar(255),
+    primary key (id)
+);
+
+
+create table jbpm_user_tasks_reassignment (
+    id                  int,
+    task_id             varchar(50) not null,
+    reassignment_type   varchar(255) not null,
+    reassignment_value  bytea,
+    java_type           varchar(255),
+    primary key (id)
+);
+
+
+create table jbpm_user_tasks_deadline_timer (
+    task_id             varchar(50) not null,
+    notification_job_id varchar(255) not null,
+    notification_type   varchar(255) not null,
+    notification_value  bytea,
+    java_type           varchar(255),
+    primary key (task_id, notification_job_id)
+);
+
+
+
+create table jbpm_user_tasks_reassignment_timer (
+    task_id             varchar(50) not null,
+    reassignment_job_id varchar(255) not null,
+    reassignment_type   varchar(255) not null,
+    reassignment_value  bytea,
+    java_type           varchar(255),
+    primary key (task_id, reassignment_job_id)
+);
+
+CREATE SEQUENCE jbpm_user_tasks_deadline_seq INCREMENT BY 50 OWNED BY jbpm_user_tasks_deadline.id;
+CREATE SEQUENCE jbpm_user_tasks_reassignment_seq INCREMENT BY 50 OWNED BY jbpm_user_tasks_reassignment.id;
+
+alter table if exists jbpm_user_tasks_deadline
+add constraint fk_jbpm_user_tasks_deadline_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_reassignment
+add constraint fk_jbpm_user_tasks_reassignment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_deadline_timer
+add constraint fk_jbpm_user_tasks_deadline_timer_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_reassignment_timer
+add constraint fk_jbpm_user_tasks_reassignment_timer_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java
index d0ca49f114..b93b54e51e 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java
@@ -24,7 +24,12 @@ import java.util.Optional;
 import java.util.function.Function;
 
 import org.assertj.core.api.Assertions;
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.CommentsEntityMapper;
+import org.jbpm.usertask.jpa.mapper.EntityMapper;
+import org.jbpm.usertask.jpa.mapper.TaskInputsEntityMapper;
+import org.jbpm.usertask.jpa.mapper.TaskMetadataEntityMapper;
+import org.jbpm.usertask.jpa.mapper.TaskOutputsEntityMapper;
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
 import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
 import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
 import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
@@ -37,7 +42,11 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
 public class JPAUserTaskInstancesTest {
@@ -46,7 +55,7 @@ public class JPAUserTaskInstancesTest {
     private UserTaskInstanceRepository userTaskInstanceRepository;
 
     @Mock
-    private AttachmentsEntityMapper attachmentsEntityMapper;
+    private EntityMapper attachmentsEntityMapper;
     @Mock
     private CommentsEntityMapper commentsEntityMapper;
     @Mock
@@ -66,7 +75,7 @@ public class JPAUserTaskInstancesTest {
 
     @BeforeEach
     public void setup() {
-        userTaskInstanceEntityMapper = spy(new UserTaskInstanceEntityMapper(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper));
+        userTaskInstanceEntityMapper = spy(new UserTaskInstanceEntityMapper(List.of(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper)));
         jpaUserTaskInstances = new JPAUserTaskInstances(userTaskInstanceRepository, userTaskInstanceEntityMapper);
         jpaUserTaskInstances.setReconnectUserTaskInstance(reconnectUserTaskInstance);
         jpaUserTaskInstances.setDisconnectUserTaskInstance(disconnectUserTaskInstance);
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java
index cafdb8e166..904dd0c619 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java
@@ -42,7 +42,7 @@ public class AttachmentsEntityMapperTest {
 
     @Mock
     private AttachmentRepository repository;
-    private AttachmentsEntityMapper mapper;
+    private EntityMapper mapper;
 
     @BeforeEach
     public void setup() {
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java
index 3b49d9b445..2cdf057b5d 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java
@@ -19,6 +19,8 @@
 
 package org.jbpm.usertask.jpa.mapper;
 
+import java.util.List;
+
 import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
 import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
 import org.junit.jupiter.api.BeforeEach;
@@ -28,7 +30,9 @@ import org.kie.kogito.usertask.UserTaskInstance;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 @ExtendWith(MockitoExtension.class)
 public class UserTaskInstanceEntityMapperTest {
@@ -48,7 +52,7 @@ public class UserTaskInstanceEntityMapperTest {
 
     @BeforeEach
     public void setUp() {
-        this.userTaskInstanceEntityMapper = new UserTaskInstanceEntityMapper(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper);
+        this.userTaskInstanceEntityMapper = new UserTaskInstanceEntityMapper(List.of(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper));
     }
 
     @Test
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java
index b1b4373f36..0d501f1478 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java
@@ -19,13 +19,21 @@
 
 package org.jbpm.usertask.jpa.mapper.utils;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
 
 import org.assertj.core.api.Assertions;
 import org.jbpm.usertask.jpa.mapper.models.Person;
 import org.jbpm.usertask.jpa.model.AttachmentEntity;
 import org.jbpm.usertask.jpa.model.CommentEntity;
-import org.jbpm.usertask.jpa.model.TaskDataEntity;
+import org.jbpm.usertask.jpa.model.TaskNamedDataEntity;
 import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
@@ -203,7 +211,7 @@ public class TestUtils {
         assertUserTaskEntityMapData(userTaskInstanceEntity.getMetadata(), userTaskInstance.getMetadata());
     }
 
-    private static void assertUserTaskEntityMapData(Collection<? extends TaskDataEntity> entityData, Map<String, Object> instanceData) {
+    private static void assertUserTaskEntityMapData(Collection<? extends TaskNamedDataEntity> entityData, Map<String, Object> instanceData) {
         Assertions.assertThat(entityData.size())
                 .isEqualTo(instanceData.size());
 
@@ -228,18 +236,18 @@ public class TestUtils {
         assertUserTaskInstanceMapData(userTaskInstance.getMetadata(), userTaskInstanceEntity.getMetadata());
     }
 
-    private static void assertUserTaskInstanceMapData(Map<String, Object> instanceData, Collection<? extends TaskDataEntity> entityData) {
+    private static void assertUserTaskInstanceMapData(Map<String, Object> instanceData, Collection<? extends TaskNamedDataEntity> entityData) {
         Assertions.assertThat(instanceData.size())
                 .isEqualTo(entityData.size());
 
         instanceData.forEach((key, value) -> {
-            Optional<? extends TaskDataEntity> optional = entityData.stream().filter(data -> data.getName().equals(key)).findFirst();
+            Optional<? extends TaskNamedDataEntity> optional = entityData.stream().filter(data -> data.getName().equals(key)).findFirst();
 
             Assertions.assertThat(optional)
                     .isPresent();
 
             if (Objects.nonNull(value)) {
-                TaskDataEntity data = optional.get();
+                TaskNamedDataEntity data = optional.get();
                 Assertions.assertThat(value.getClass().getName())
                         .isEqualTo(data.getJavaType());
             }
@@ -262,6 +270,16 @@ public class TestUtils {
         instance.setAdminGroups(Set.of("Administrators", "Managers"));
         instance.setExcludedUsers(Set.of("Ned", "Bart"));
 
+        instance.setNotStartedDeadlines(new ArrayList<>());
+        instance.setNotStartedDeadlinesTimers(new HashMap<>());
+        instance.setNotCompletedDeadlines(new ArrayList<>());
+        instance.setNotCompletedDeadlinesTimers(new HashMap<>());
+
+        instance.setNotStartedReassignments(new ArrayList<>());
+        instance.setNotStartedReassignmentsTimers(new HashMap<>());
+        instance.setNotCompletedReassignments(new ArrayList<>());
+        instance.setNotCompletedReassignmentsTimers(new HashMap<>());
+
         instance.setExternalReferenceId("external-reference-id");
 
         instance.setMetadata("ProcessId", "process-id");
diff --git a/addons/common/jobs/api/src/main/java/org/kie/kogito/jobs/api/JobCallbackResourceDef.java b/addons/common/jobs/api/src/main/java/org/kie/kogito/jobs/api/JobCallbackResourceDef.java
index ba5cfd6ac0..3bb36df752 100644
--- a/addons/common/jobs/api/src/main/java/org/kie/kogito/jobs/api/JobCallbackResourceDef.java
+++ b/addons/common/jobs/api/src/main/java/org/kie/kogito/jobs/api/JobCallbackResourceDef.java
@@ -20,7 +20,8 @@ package org.kie.kogito.jobs.api;
 
 import java.time.temporal.ChronoUnit;
 
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.service.api.TemporalUnit;
 import org.kie.kogito.jobs.service.api.recipient.http.HttpRecipient;
 import org.kie.kogito.jobs.service.api.recipient.http.HttpRecipientJsonPayloadData;
@@ -53,25 +54,16 @@ public class JobCallbackResourceDef {
 
     public static final String LIMIT_DEFAULT_VALUE = "0";
 
-    public static final String JOBS_CALLBACK_URI = "/management/jobs";
-
     public static final String JOBS_CALLBACK_POST_URI = "{" + PROCESS_ID + "}/instances/{" + PROCESS_INSTANCE_ID + "}/timers/{" + TIMER_ID + "}";
 
     private JobCallbackResourceDef() {
     }
 
-    public static String buildCallbackURI(ProcessInstanceJobDescription description, String jobsCallbackEndpoint) {
-        return URIBuilder.toURI(jobsCallbackEndpoint
-                + JOBS_CALLBACK_URI + "/"
-                + description.processId()
-                + "/instances/"
-                + description.processInstanceId()
-                + "/timers/"
-                + description.timerId())
-                .toString();
+    public static String buildCallbackURI(JobDescription description, String jobsCallbackEndpoint) {
+        return URIBuilder.toURI(jobsCallbackEndpoint + description.path()).toString();
     }
 
-    public static org.kie.kogito.jobs.service.api.Job buildCallbackPatternJob(ProcessInstanceJobDescription description,
+    public static org.kie.kogito.jobs.service.api.Job buildCallbackPatternJob(JobDescription description,
             String callback, ObjectMapper objectMapper) {
         return org.kie.kogito.jobs.service.api.Job.builder()
                 .id(description.id())
@@ -81,25 +73,28 @@ public class JobCallbackResourceDef {
                 .build();
     }
 
-    private static HttpRecipient<HttpRecipientJsonPayloadData> buildRecipient(ProcessInstanceJobDescription description, String callback, ObjectMapper objectMapper) {
-        return HttpRecipient.builder()
+    private static HttpRecipient<HttpRecipientJsonPayloadData> buildRecipient(JobDescription description, String callback, ObjectMapper objectMapper) {
+        HttpRecipient.Builder<HttpRecipientJsonPayloadData> selector = HttpRecipient.builder()
                 .forJsonPayload()
                 .payload(HttpRecipientJsonPayloadData.from(buildPayload(description, objectMapper)))
                 .url(callback)
-                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .header(PROCESS_ID, description.processId())
-                .header(PROCESS_INSTANCE_ID, description.processInstanceId())
-                .header(ROOT_PROCESS_ID, description.rootProcessId())
-                .header(ROOT_PROCESS_INSTANCE_ID, description.rootProcessInstanceId())
-                .header(NODE_INSTANCE_ID, description.nodeInstanceId())
-                .build();
+                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+
+        if (description instanceof ProcessInstanceJobDescription processInstanceJobDescription) {
+            selector.header(PROCESS_ID, processInstanceJobDescription.processId())
+                    .header(PROCESS_INSTANCE_ID, processInstanceJobDescription.processInstanceId())
+                    .header(ROOT_PROCESS_ID, processInstanceJobDescription.rootProcessId())
+                    .header(ROOT_PROCESS_INSTANCE_ID, processInstanceJobDescription.rootProcessInstanceId())
+                    .header(NODE_INSTANCE_ID, processInstanceJobDescription.nodeInstanceId());
+        }
+        return selector.build();
     }
 
-    private static JsonNode buildPayload(ProcessInstanceJobDescription description, ObjectMapper objectMapper) {
+    private static JsonNode buildPayload(JobDescription description, ObjectMapper objectMapper) {
         return objectMapper.valueToTree(new JobCallbackPayload(description.id()));
     }
 
-    public static TimerSchedule buildSchedule(ProcessInstanceJobDescription description) {
+    public static TimerSchedule buildSchedule(JobDescription description) {
         return TimerSchedule.builder()
                 .startTime(description.expirationTime().get().toOffsetDateTime().truncatedTo(ChronoUnit.MILLIS))
                 .repeatCount(translateLimit(description.expirationTime().repeatLimit()))
diff --git a/addons/common/jobs/api/src/test/java/org/kie/kogito/jobs/api/JobCallbackResourceDefTest.java b/addons/common/jobs/api/src/test/java/org/kie/kogito/jobs/api/JobCallbackResourceDefTest.java
index ee9b7aec67..060a0470b8 100644
--- a/addons/common/jobs/api/src/test/java/org/kie/kogito/jobs/api/JobCallbackResourceDefTest.java
+++ b/addons/common/jobs/api/src/test/java/org/kie/kogito/jobs/api/JobCallbackResourceDefTest.java
@@ -23,7 +23,7 @@ import java.net.URI;
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.service.api.recipient.http.HttpRecipient;
 import org.kie.kogito.jobs.service.api.schedule.timer.TimerSchedule;
 import org.kie.kogito.jobs.service.api.serlialization.SerializationUtils;
@@ -91,7 +91,7 @@ class JobCallbackResourceDefTest {
     }
 
     private ProcessInstanceJobDescription mockProcessInstanceJobDescription() {
-        return ProcessInstanceJobDescription.builder()
+        return ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                 .id(JOB_ID)
                 .timerId(TIMER_ID)
                 .expirationTime(EXPIRATION_TIME)
diff --git a/addons/common/jobs/management-common/src/main/java/org/kie/kogito/jobs/management/RestJobsService.java b/addons/common/jobs/management-common/src/main/java/org/kie/kogito/jobs/management/RestJobsService.java
index a503015d3b..615b40b5cd 100644
--- a/addons/common/jobs/management-common/src/main/java/org/kie/kogito/jobs/management/RestJobsService.java
+++ b/addons/common/jobs/management-common/src/main/java/org/kie/kogito/jobs/management/RestJobsService.java
@@ -21,8 +21,8 @@ package org.kie.kogito.jobs.management;
 import java.net.URI;
 import java.util.Objects;
 
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.api.URIBuilder;
 import org.kie.kogito.jobs.service.api.Job;
 
@@ -46,7 +46,7 @@ public abstract class RestJobsService implements JobsService {
         this.objectMapper = objectMapper;
     }
 
-    public String getCallbackEndpoint(ProcessInstanceJobDescription description) {
+    public String getCallbackEndpoint(JobDescription description) {
         return buildCallbackURI(description, callbackEndpoint);
     }
 
@@ -58,7 +58,7 @@ public abstract class RestJobsService implements JobsService {
         return jobsServiceUri;
     }
 
-    public Job buildJob(ProcessInstanceJobDescription description, String callback) {
+    public Job buildJob(JobDescription description, String callback) {
         return buildCallbackPatternJob(description, callback, objectMapper);
     }
 }
diff --git a/addons/common/jobs/management-common/src/test/java/org/kie/kogito/jobs/management/RestJobsServiceTest.java b/addons/common/jobs/management-common/src/test/java/org/kie/kogito/jobs/management/RestJobsServiceTest.java
index 193ceb92a1..0f0b2d3f5d 100644
--- a/addons/common/jobs/management-common/src/test/java/org/kie/kogito/jobs/management/RestJobsServiceTest.java
+++ b/addons/common/jobs/management-common/src/test/java/org/kie/kogito/jobs/management/RestJobsServiceTest.java
@@ -25,9 +25,8 @@ import java.time.temporal.ChronoUnit;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.jobs.ExactExpirationTime;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
 import org.kie.kogito.jobs.api.JobCallbackPayload;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.service.api.Job;
 import org.kie.kogito.jobs.service.api.recipient.http.HttpRecipient;
 import org.kie.kogito.jobs.service.api.recipient.http.HttpRecipientJsonPayloadData;
@@ -37,7 +36,6 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public abstract class RestJobsServiceTest<T extends RestJobsService> {
 
@@ -64,7 +62,7 @@ public abstract class RestJobsServiceTest<T extends RestJobsService> {
 
     @Test
     void testGetCallbackEndpoint() {
-        ProcessInstanceJobDescription description = ProcessInstanceJobDescription.builder()
+        ProcessInstanceJobDescription description = ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                 .id(JOB_ID)
                 .timerId(TIMER_ID)
                 .expirationTime(ExactExpirationTime.now())
@@ -86,17 +84,8 @@ public abstract class RestJobsServiceTest<T extends RestJobsService> {
         assertThat(jobsServiceUri).hasToString(JOB_SERVICE_URL + "/v2/jobs");
     }
 
-    @Test
-    void testScheduleProcessJob() {
-        ProcessJobDescription processJobDescription = ProcessJobDescription.of(ExactExpirationTime.of(EXPIRATION_TIME),
-                1,
-                PROCESS_ID);
-        assertThatThrownBy(() -> tested.scheduleProcessJob(processJobDescription))
-                .isInstanceOf(UnsupportedOperationException.class);
-    }
-
     protected ProcessInstanceJobDescription buildProcessInstanceJobDescription() {
-        return ProcessInstanceJobDescription.builder()
+        return ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                 .id(JOB_ID)
                 .timerId(TIMER_ID)
                 .expirationTime(ExactExpirationTime.of(EXPIRATION_TIME))
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/StaticConfig.java b/api/kogito-api/src/main/java/org/kie/kogito/StaticConfig.java
index cbfbe80afa..dd67a4cc19 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/StaticConfig.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/StaticConfig.java
@@ -18,22 +18,33 @@
  */
 package org.kie.kogito;
 
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
+import static java.util.Arrays.asList;
+
 public class StaticConfig implements Config {
 
-    private final Addons addons;
-    private final Map<Class<? extends KogitoConfig>, KogitoConfig> configMap = new HashMap<>();
+    private Addons addons;
+    private Map<Class<? extends KogitoConfig>, KogitoConfig> configMap = new HashMap<>();
+
+    public StaticConfig() {
+        addons = Addons.EMTPY;
+    }
+
+    public StaticConfig(Addons addons, KogitoConfig... configs) {
+        init(addons, configs);
+    }
+
+    protected StaticConfig(Addons addons, Iterable<KogitoConfig> configs) {
+        init(addons, configs);
+    }
 
-    public StaticConfig(Addons addons,
-            KogitoConfig... configs) {
-        this(addons, Arrays.asList(configs));
+    protected void init(Addons addons, KogitoConfig... configs) {
+        init(addons, asList(configs));
     }
 
-    protected StaticConfig(Addons addons,
-            Iterable<KogitoConfig> configs) {
+    protected void init(Addons addons, Iterable<KogitoConfig> configs) {
         this.addons = addons;
         configs.forEach(this::loadConfig);
     }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
index 5bbd520bfc..f69c74e6c2 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobDescription.java
@@ -20,9 +20,14 @@ package org.kie.kogito.jobs;
 
 public interface JobDescription {
 
+    static final String JOBS_CALLBACK_URI = "/management/jobs";
+
     String id();
 
     ExpirationTime expirationTime();
 
     Integer priority();
+
+    String path();
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobsService.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobsService.java
index a9e5230c40..f953c9a79b 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobsService.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/JobsService.java
@@ -29,19 +29,11 @@ public interface JobsService {
      * Schedules process job that is responsible for starting new process instances
      * based on the given description.
      * 
+     * @param context of the job
      * @param description defines what kind of process should be started upon expiration time
      * @return returns unique id of the job
      */
-    String scheduleProcessJob(ProcessJobDescription description);
-
-    /**
-     * Schedules process instance related job that will signal exact same process instance
-     * upon expiration time.
-     * 
-     * @param description defines the context of the process instance that should be signaled
-     * @return returns unique id of the job
-     */
-    String scheduleProcessInstanceJob(ProcessInstanceJobDescription description);
+    String scheduleJob(JobDescription description);
 
     /**
      * Cancels given job
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescription.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescription.java
similarity index 88%
rename from api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescription.java
rename to api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescription.java
index 086bfac244..3f0becb82e 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescription.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescription.java
@@ -16,7 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.kie.kogito.jobs.descriptors;
+
+import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
 
 import static java.util.Objects.requireNonNull;
 
@@ -93,10 +96,21 @@ public class ProcessInstanceJobDescription implements JobDescription {
         return nodeInstanceId;
     }
 
-    public static ProcessInstanceJobDescriptionBuilder builder() {
+    public static ProcessInstanceJobDescriptionBuilder newProcessInstanceJobDescriptionBuilder() {
         return new ProcessInstanceJobDescriptionBuilder();
     }
 
+    @Override
+    public String path() {
+        return JOBS_CALLBACK_URI + "/"
+                + processId()
+                + "/instances/"
+                + processInstanceId()
+                + "/timers/"
+                + timerId();
+
+    }
+
     @Override
     public String toString() {
         return "ProcessInstanceJobDescription{" +
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescriptionBuilder.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescriptionBuilder.java
similarity index 97%
rename from api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescriptionBuilder.java
rename to api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescriptionBuilder.java
index 73e491aefe..0c9c203442 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessInstanceJobDescriptionBuilder.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessInstanceJobDescriptionBuilder.java
@@ -16,10 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.kie.kogito.jobs.descriptors;
 
 import java.util.UUID;
 
+import org.kie.kogito.jobs.ExpirationTime;
+
 public class ProcessInstanceJobDescriptionBuilder {
 
     private String id;
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessJobDescription.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessJobDescription.java
similarity index 93%
rename from api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessJobDescription.java
rename to api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessJobDescription.java
index d3f3d8d177..e8ab6baf01 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/jobs/ProcessJobDescription.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/ProcessJobDescription.java
@@ -16,10 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.jobs;
+package org.kie.kogito.jobs.descriptors;
 
 import java.util.UUID;
 
+import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.process.Process;
 
 import static java.util.Objects.requireNonNull;
@@ -87,4 +89,9 @@ public class ProcessJobDescription implements JobDescription {
     public Process<?> process() {
         return process;
     }
+
+    @Override
+    public String path() {
+        return "";
+    }
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescription.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescription.java
new file mode 100644
index 0000000000..5fbe5f29ed
--- /dev/null
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescription.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.jobs.descriptors;
+
+import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
+
+public class UserTaskInstanceJobDescription implements JobDescription {
+
+    private String id;
+    private ExpirationTime expirationTime;
+    private Integer priority = ProcessInstanceJobDescription.DEFAULT_PRIORITY;
+    private String userTaskInstanceId;
+
+    public UserTaskInstanceJobDescription() {
+        // do nothing
+    }
+
+    public UserTaskInstanceJobDescription(String id, ExpirationTime expirationTime, Integer priority, String userTaskInstanceId) {
+        this.id = id;
+        this.expirationTime = expirationTime;
+        this.priority = priority;
+        this.userTaskInstanceId = userTaskInstanceId;
+    }
+
+    @Override
+    public String id() {
+        return id;
+    }
+
+    @Override
+    public ExpirationTime expirationTime() {
+        return expirationTime;
+    }
+
+    @Override
+    public Integer priority() {
+        return priority;
+    }
+
+    @Override
+    public String path() {
+        return null;
+    }
+
+    public String getUserTaskInstanceId() {
+        return userTaskInstanceId;
+    }
+
+    public static UserTaskInstanceJobDescriptionBuilder newUserTaskInstanceJobDescriptionBuilder() {
+        return new UserTaskInstanceJobDescriptionBuilder();
+    }
+}
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescriptionBuilder.java b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescriptionBuilder.java
new file mode 100644
index 0000000000..ad8a4bfba2
--- /dev/null
+++ b/api/kogito-api/src/main/java/org/kie/kogito/jobs/descriptors/UserTaskInstanceJobDescriptionBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.jobs.descriptors;
+
+import java.util.UUID;
+
+import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
+
+public class UserTaskInstanceJobDescriptionBuilder implements JobDescription {
+
+    private String id;
+    private ExpirationTime expirationTime;
+    private Integer priority = ProcessInstanceJobDescription.DEFAULT_PRIORITY;
+    private String userTaskInstanceId;
+
+    @Override
+    public String id() {
+        return id;
+    }
+
+    @Override
+    public ExpirationTime expirationTime() {
+        return expirationTime;
+    }
+
+    @Override
+    public Integer priority() {
+        return priority;
+    }
+
+    @Override
+    public String path() {
+        return null;
+    }
+
+    public UserTaskInstanceJobDescriptionBuilder id(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public UserTaskInstanceJobDescriptionBuilder generateId() {
+        return id(UUID.randomUUID().toString());
+    }
+
+    public UserTaskInstanceJobDescriptionBuilder expirationTime(ExpirationTime expirationTime) {
+        this.expirationTime = expirationTime;
+        return this;
+    }
+
+    public UserTaskInstanceJobDescriptionBuilder priority(Integer priority) {
+        this.priority = priority;
+        return this;
+    }
+
+    public UserTaskInstanceJobDescriptionBuilder userTaskInstanceId(String userTaskInstanceId) {
+        this.userTaskInstanceId = userTaskInstanceId;
+        return this;
+    }
+
+    public UserTaskInstanceJobDescription build() {
+        return new UserTaskInstanceJobDescription(id, expirationTime, priority, userTaskInstanceId);
+    }
+}
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java
index b07df5f2f3..1a8b8f80b8 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java
@@ -19,10 +19,10 @@
 package org.kie.kogito.usertask;
 
 import java.util.Collection;
-import java.util.Map;
 import java.util.Set;
 
 import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
 import org.kie.kogito.usertask.model.Reassignment;
 
 /**
@@ -101,13 +101,13 @@ public interface UserTask {
      */
     Set<String> getExcludedUsers();
 
-    Collection<DeadlineInfo<Map<String, Object>>> getNotStartedDeadlines();
+    Collection<DeadlineInfo<Notification>> getNotStartedDeadlines();
 
-    Collection<DeadlineInfo<Map<String, Object>>> getNotCompletedDeadlines();
+    Collection<DeadlineInfo<Notification>> getNotCompletedDeadlines();
 
     Collection<DeadlineInfo<Reassignment>> getNotStartedReassignments();
 
-    Collection<DeadlineInfo<Reassignment>> getNotCompletedReassigments();
+    Collection<DeadlineInfo<Reassignment>> getNotCompletedReassignments();
 
     UserTaskAssignmentStrategy getAssignmentStrategy();
 
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java
index e28e23befe..6fcdd84c6f 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java
@@ -23,9 +23,13 @@ import java.util.Map;
 import java.util.Set;
 
 import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.jobs.descriptors.UserTaskInstanceJobDescription;
 import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.model.Attachment;
 import org.kie.kogito.usertask.model.Comment;
+import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
+import org.kie.kogito.usertask.model.Reassignment;
 
 public interface UserTaskInstance {
 
@@ -43,6 +47,8 @@ public interface UserTaskInstance {
 
     String getActualOwner();
 
+    void initialize(Map<String, Object> data, IdentityProvider identity);
+
     void transition(String transitionId, Map<String, Object> data, IdentityProvider identityProvider);
 
     String getExternalReferenceId();
@@ -118,4 +124,30 @@ public interface UserTaskInstance {
 
     Collection<Comment> getComments();
 
+    void trigger(UserTaskInstanceJobDescription userTaskInstanceJobDescription);
+
+    Collection<DeadlineInfo<Notification>> getNotStartedDeadlines();
+
+    Collection<DeadlineInfo<Notification>> getNotCompletedDeadlines();
+
+    Collection<DeadlineInfo<Reassignment>> getNotStartedReassignments();
+
+    Collection<DeadlineInfo<Reassignment>> getNotCompletedReassignments();
+
+    void startNotStartedDeadlines();
+
+    void startNotCompletedDeadlines();
+
+    void startNotStartedReassignments();
+
+    void startNotCompletedReassignments();
+
+    void stopNotStartedDeadlines();
+
+    void stopNotStartedReassignments();
+
+    void stopNotCompletedDeadlines();
+
+    void stopNotCompletedReassignments();
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java
index 09170f4d1c..43b8b66950 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java
@@ -28,13 +28,23 @@ import org.kie.kogito.usertask.UserTaskInstance;
 
 public interface UserTaskLifeCycle {
 
+    final String ACTIVATE = "activate";
+
+    default String startTransition() {
+        return ACTIVATE;
+    }
+
     Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition, IdentityProvider identity);
 
     UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map<String, Object> data);
 
-    UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> emptyMap);
+    default Optional<UserTaskTransitionToken> newReassignmentTransitionToken(UserTaskInstance defaultUserTaskInstance, Map<String, Object> data) {
+        return Optional.empty();
+    }
+
+    UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data);
 
-    UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> emptyMap);
+    UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data);
 
     List<UserTaskTransition> allowedTransitions(UserTaskInstance ut);
 
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Notification.java
similarity index 56%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Notification.java
index 024532fcf1..0146d3cd70 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Notification.java
@@ -18,45 +18,50 @@
  */
 package org.kie.kogito.usertask.model;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 
-public class Reassignment {
+public class Notification {
 
-    private Set<String> potentialUsers;
-    private Set<String> potentialGroups;
+    private Map<String, Object> data;
 
-    public Reassignment(Set<String> potentialUsers, Set<String> potentialGroups) {
-        this.potentialUsers = potentialUsers;
-        this.potentialGroups = potentialGroups;
+    public Notification() {
+        data = new HashMap<>();
     }
 
-    public Set<String> getPotentialUsers() {
-        return potentialUsers;
+    public Notification(Map<String, Object> data) {
+        this.data = data;
     }
 
-    public Set<String> getPotentialGroups() {
-        return potentialGroups;
+    public Map<String, Object> getData() {
+        return data;
+    }
+
+    public void setData(Map<String, Object> data) {
+        this.data = data;
     }
 
     @Override
     public String toString() {
-        return "Reassigment [potentialUsers=" + potentialUsers + ", potentialGroups=" + potentialGroups + "]";
+        return "Notification [data=" + data + "]";
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(potentialGroups, potentialUsers);
+        return Objects.hash(data);
     }
 
     @Override
     public boolean equals(Object obj) {
         if (this == obj)
             return true;
-        if (!(obj instanceof Reassignment))
+        if (obj == null)
             return false;
-        Reassignment other = (Reassignment) obj;
-        return Objects.equals(potentialGroups, other.potentialGroups) && Objects.equals(potentialUsers,
-                other.potentialUsers);
+        if (getClass() != obj.getClass())
+            return false;
+        Notification other = (Notification) obj;
+        return Objects.equals(data, other.data);
     }
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java
index 024532fcf1..89bda5921f 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Reassignment.java
@@ -26,6 +26,17 @@ public class Reassignment {
     private Set<String> potentialUsers;
     private Set<String> potentialGroups;
 
+    public Reassignment() {
+    }
+
+    public void setPotentialGroups(Set<String> potentialGroups) {
+        this.potentialGroups = potentialGroups;
+    }
+
+    public void setPotentialUsers(Set<String> potentialUsers) {
+        this.potentialUsers = potentialUsers;
+    }
+
     public Reassignment(Set<String> potentialUsers, Set<String> potentialGroups) {
         this.potentialUsers = potentialUsers;
         this.potentialGroups = potentialGroups;
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobContext.java
similarity index 67%
copy from kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java
copy to api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobContext.java
index 74e3c06683..ed98f33dda 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobContext.java
@@ -16,16 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package $Package$;
+package org.kie.kogito.services.jobs.impl;
 
-import java.util.Collection;
+import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.uow.UnitOfWorkManager;
+import org.kie.kogito.usertask.UserTasks;
 
-@org.springframework.stereotype.Component
-public class ApplicationConfig extends org.kie.kogito.StaticConfig {
+public record InMemoryJobContext(KogitoProcessRuntime runtime, UnitOfWorkManager unitOfWorkManager, Processes processes, UserTasks userTasks) {
 
-    @org.springframework.beans.factory.annotation.Autowired
-    public ApplicationConfig(
-            Collection<org.kie.kogito.KogitoConfig> configs) {
-        super($Addons$, configs);
-    }
-}
+}
\ No newline at end of file
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java
index c3ea0ad7f8..f48f8b9c2b 100644
--- a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java
@@ -20,7 +20,9 @@ package org.kie.kogito.services.jobs.impl;
 
 import java.time.Duration;
 import java.time.ZonedDateTime;
-import java.util.Objects;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledExecutorService;
@@ -28,16 +30,8 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import org.kie.kogito.Model;
 import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
-import org.kie.kogito.process.Process;
-import org.kie.kogito.process.ProcessInstanceOptimisticLockingException;
-import org.kie.kogito.process.Processes;
-import org.kie.kogito.services.uow.UnitOfWorkExecutor;
-import org.kie.kogito.uow.UnitOfWorkManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,74 +39,59 @@ public class InMemoryJobService implements JobsService, AutoCloseable {
 
     public static final String IN_MEMORY_JOB_SERVICE_POOL_SIZE_PROPERTY = "kogito.in-memory.job-service.pool-size";
     private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryJobService.class);
-    protected static final String TRIGGER = "timer";
 
-    protected final ScheduledExecutorService scheduler;
-    protected final UnitOfWorkManager unitOfWorkManager;
+    protected ScheduledExecutorService scheduler;
 
-    protected ConcurrentHashMap<String, ScheduledFuture<?>> scheduledJobs = new ConcurrentHashMap<>();
-    private final Processes processes;
+    protected ConcurrentHashMap<String, ScheduledFuture<?>> scheduledJobs;
+    protected List<JobExecutorFactory> jobExecutorFactories;
 
-    private static final ConcurrentHashMap<Integer, InMemoryJobService> INSTANCE = new ConcurrentHashMap<>();
-
-    protected InMemoryJobService(Processes processes, UnitOfWorkManager unitOfWorkManager) {
-        this(processes, unitOfWorkManager, new ScheduledThreadPoolExecutor(Integer.parseInt(System.getProperty(IN_MEMORY_JOB_SERVICE_POOL_SIZE_PROPERTY, "10"))));
+    public InMemoryJobService() {
+        this(new ScheduledThreadPoolExecutor(Integer.parseInt(System.getProperty(IN_MEMORY_JOB_SERVICE_POOL_SIZE_PROPERTY, "10"))));
     }
 
-    protected InMemoryJobService(Processes processes, UnitOfWorkManager unitOfWorkManager, ScheduledExecutorService scheduler) {
-        this.processes = processes;
-        this.unitOfWorkManager = unitOfWorkManager;
+    public InMemoryJobService(ScheduledExecutorService scheduler) {
+        this.scheduledJobs = new ConcurrentHashMap<>();
+        this.jobExecutorFactories = new ArrayList<>();
         this.scheduler = scheduler;
     }
 
-    public static InMemoryJobService get(Processes processes, UnitOfWorkManager unitOfWorkManager) {
-        Objects.requireNonNull(processes);
-        Objects.requireNonNull(unitOfWorkManager);
-        return INSTANCE.computeIfAbsent(processes.hashCode() + 7 * unitOfWorkManager.hashCode(), k -> new InMemoryJobService(processes, unitOfWorkManager));
-    }
-
-    public static InMemoryJobService get(Processes processes, UnitOfWorkManager unitOfWorkManager, ScheduledExecutorService scheduler) {
-        Objects.requireNonNull(processes);
-        Objects.requireNonNull(unitOfWorkManager);
-        Objects.requireNonNull(scheduler);
-        return INSTANCE.computeIfAbsent(processes.hashCode() + 7 * unitOfWorkManager.hashCode(), k -> new InMemoryJobService(processes, unitOfWorkManager, scheduler));
+    public InMemoryJobService registerJobExecutorFactory(JobExecutorFactory jobExecutorFactory) {
+        Iterator<JobExecutorFactory> iterator = this.jobExecutorFactories.iterator();
+        while (iterator.hasNext()) {
+            JobExecutorFactory factory = iterator.next();
+            if (factory.types().containsAll(jobExecutorFactory.types())) {
+                iterator.remove();
+            }
+        }
+        this.jobExecutorFactories.add(jobExecutorFactory);
+        return this;
     }
 
     @Override
-    public String scheduleProcessJob(ProcessJobDescription description) {
-        LOGGER.debug("ScheduleProcessJob: {}", description);
+    public String scheduleJob(JobDescription jobDescription) {
+        LOGGER.debug("ScheduleProcessJob: {}", jobDescription);
         ScheduledFuture<?> future;
-        if (description.expirationTime().repeatInterval() != null) {
-            future = scheduler.scheduleAtFixedRate(repeatableProcessJobByDescription(description), calculateDelay(description), description.expirationTime().repeatInterval(), TimeUnit.MILLISECONDS);
-        } else {
-            future = scheduler.schedule(processJobByDescription(description), calculateDelay(description), TimeUnit.MILLISECONDS);
+        long delay = calculateDelay(jobDescription);
+        Long interval = jobDescription.expirationTime().repeatInterval();
+        Optional<JobExecutorFactory> jobExecutorFactoryFound = findJobExecutorFactory(jobDescription);
+
+        if (jobExecutorFactoryFound.isEmpty()) {
+            throw new IllegalArgumentException("Could not schedule " + jobDescription + ". No job executor factory provided");
         }
-        scheduledJobs.put(description.id(), future);
-        return description.id();
-    }
 
-    @Override
-    public String scheduleProcessInstanceJob(ProcessInstanceJobDescription description) {
-        ScheduledFuture<?> future;
-        if (description.expirationTime().repeatInterval() != null) {
-            future = scheduler.scheduleAtFixedRate(
-                    getSignalProcessInstanceCommand(description, false, description.expirationTime().repeatLimit()),
-                    calculateDelay(description), description.expirationTime().repeatInterval(), TimeUnit.MILLISECONDS);
+        JobExecutorFactory jobExecutorFactory = jobExecutorFactoryFound.get();
+
+        if (interval != null) {
+            future = scheduler.scheduleAtFixedRate(jobExecutorFactory.createNewRepeteableRunnable(this, jobDescription), delay, interval, TimeUnit.MILLISECONDS);
         } else {
-            future = scheduler.schedule(getSignalProcessInstanceCommand(description, true, 1), calculateDelay(description),
-                    TimeUnit.MILLISECONDS);
+            future = scheduler.schedule(jobExecutorFactory.createNewRunnable(this, jobDescription), delay, TimeUnit.MILLISECONDS);
         }
-        scheduledJobs.put(description.id(), future);
-        return description.id();
+        scheduledJobs.put(jobDescription.id(), future);
+        return jobDescription.id();
     }
 
-    public Runnable getSignalProcessInstanceCommand(ProcessInstanceJobDescription description, boolean remove, int limit) {
-        return new SignalProcessInstanceOnExpiredTimer(
-                description.id(),
-                description.timerId(),
-                description.processInstanceId(),
-                remove,
-                limit);
+    private Optional<JobExecutorFactory> findJobExecutorFactory(JobDescription jobDescription) {
+        return jobExecutorFactories.stream().filter(factory -> factory.accept(jobDescription)).findFirst();
     }
 
     @Override
@@ -139,102 +118,15 @@ public class InMemoryJobService implements JobsService, AutoCloseable {
         return delay;
     }
 
-    protected Runnable processJobByDescription(ProcessJobDescription description) {
-        return new StartProcessOnExpiredTimer(description.id(), description.process(), true, -1);
-    }
-
-    protected Runnable repeatableProcessJobByDescription(ProcessJobDescription description) {
-        return new StartProcessOnExpiredTimer(description.id(), description.process(), false, description.expirationTime().repeatLimit());
-    }
-
-    private class SignalProcessInstanceOnExpiredTimer implements Runnable {
-
-        private final String id;
-        private final String timerId;
-        private boolean removeAtExecution;
-        private String processInstanceId;
-        private Integer limit;
-
-        private SignalProcessInstanceOnExpiredTimer(String id, String timerId, String processInstanceId, boolean removeAtExecution, Integer limit) {
-            this.id = id;
-            this.timerId = timerId;
-            this.processInstanceId = processInstanceId;
-            this.removeAtExecution = removeAtExecution;
-            this.limit = limit;
-        }
-
-        @Override
-        public void run() {
-            try {
-                Optional<Process<? extends Model>> process = processes.processByProcessInstanceId(processInstanceId);
-                if (process.isEmpty()) {
-                    LOGGER.info("Skipping Job {}. There is no process for pid {} ", id, processInstanceId);
-                    return;
-                }
-                LOGGER.info("Job {} started", id);
-                limit--;
-                boolean executed = new TriggerJobCommand(processInstanceId, id, timerId, limit, process.get(), unitOfWorkManager).execute();
-                if (limit == 0 || !executed) {
-                    cancelJob(id, false);
-                }
-                LOGGER.debug("Job {} completed", id);
-            } catch (ProcessInstanceOptimisticLockingException ex) {
-                LOGGER.info("Retrying Job {} due to: {}", id, ex.getMessage());
-                limit++;
-                run();
-            } finally {
-                if (removeAtExecution) {
-                    cancelJob(id, false);
-                }
-            }
-        }
-    }
-
-    private class StartProcessOnExpiredTimer implements Runnable {
-
-        private final String id;
-
-        private boolean removeAtExecution;
-        @SuppressWarnings("rawtypes")
-        private org.kie.kogito.process.Process process;
-
-        private Integer limit;
-
-        private StartProcessOnExpiredTimer(String id, org.kie.kogito.process.Process<?> process, boolean removeAtExecution, Integer limit) {
-            this.id = id;
-            this.process = process;
-            this.removeAtExecution = removeAtExecution;
-            this.limit = limit;
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public void run() {
-            try {
-                LOGGER.debug("Job {} started", id);
-                UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> {
-                    org.kie.kogito.process.ProcessInstance<?> pi = process.createInstance(process.createModel());
-                    if (pi != null) {
-                        pi.start(TRIGGER, null);
-                    }
-                    return null;
-                });
-                limit--;
-                if (limit == 0) {
-                    cancelJob(id, true);
-                }
-                LOGGER.debug("Job {} completed", id);
-            } finally {
-                if (removeAtExecution) {
-                    cancelJob(id, false);
-                }
-            }
-        }
-    }
-
     @Override
     public void close() throws Exception {
+        LOGGER.info("closing in memory job service");
         scheduledJobs.clear();
-        scheduler.shutdown();
+        scheduledJobs.forEach((k, v) -> v.cancel(true));
+        scheduler.shutdownNow();
+    }
+
+    public void clearJobExecutorFactories() {
+        jobExecutorFactories.clear();
     }
 }
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryProcessJobExecutorFactory.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryProcessJobExecutorFactory.java
new file mode 100644
index 0000000000..00ed9f8f49
--- /dev/null
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryProcessJobExecutorFactory.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.services.jobs.impl;
+
+import java.util.Optional;
+import java.util.Set;
+
+import org.kie.kogito.Model;
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessJobDescription;
+import org.kie.kogito.process.Process;
+import org.kie.kogito.process.ProcessInstanceOptimisticLockingException;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InMemoryProcessJobExecutorFactory implements JobExecutorFactory {
+
+    private InMemoryJobContext jobsConfiguration;
+
+    public InMemoryProcessJobExecutorFactory(InMemoryJobContext jobsConfiguration) {
+        this.jobsConfiguration = jobsConfiguration;
+    }
+
+    @Override
+    public Set<Class<? extends JobDescription>> types() {
+        return Set.of(ProcessInstanceJobDescription.class, ProcessJobDescription.class);
+    }
+
+    @Override
+    public Runnable createNewRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof ProcessInstanceJobDescription processInstanceJobDescription) {
+            return processInstanceJobDescription(jobService, jobsConfiguration, processInstanceJobDescription, true, 1);
+        } else if (jobDescription instanceof ProcessJobDescription processJobDescription) {
+            return processJobByDescription(jobService, jobsConfiguration, processJobDescription);
+        }
+        throw new IllegalArgumentException("single job description not supported for " + jobDescription);
+    }
+
+    @Override
+    public Runnable createNewRepeteableRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof ProcessInstanceJobDescription processInstanceJobDescription) {
+            return processInstanceJobDescription(jobService, jobsConfiguration, processInstanceJobDescription, false, processInstanceJobDescription.expirationTime().repeatLimit());
+        } else if (jobDescription instanceof ProcessJobDescription processJobDescription) {
+            return repeatableJobByDescription(jobService, jobsConfiguration, processJobDescription);
+        }
+        throw new IllegalArgumentException("repeteable job description not supported for " + jobDescription);
+    }
+
+    protected Runnable processInstanceJobDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessInstanceJobDescription description, boolean remove, int limit) {
+        return new SignalProcessInstanceOnExpiredTimer(
+                jobService,
+                jobsConfiguration,
+                description.id(),
+                description.timerId(),
+                description.processInstanceId(),
+                remove,
+                limit);
+    }
+
+    protected Runnable processJobByDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessJobDescription description) {
+        return new StartProcessOnExpiredTimer(jobService, jobsConfiguration, description.id(), description.process(), true, -1);
+    }
+
+    protected Runnable repeatableJobByDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessJobDescription description) {
+        return new StartProcessOnExpiredTimer(jobService, jobsConfiguration, description.id(), description.process(), false, description.expirationTime().repeatLimit());
+    }
+
+}
+
+class SignalProcessInstanceOnExpiredTimer implements Runnable {
+
+    private static Logger LOGGER = LoggerFactory.getLogger(SignalProcessInstanceOnExpiredTimer.class);
+
+    private final String id;
+    private final String timerId;
+    private boolean removeAtExecution;
+    private String processInstanceId;
+    private Integer limit;
+    private JobsService jobService;
+
+    private InMemoryJobContext jobsConfiguration;
+
+    public SignalProcessInstanceOnExpiredTimer(JobsService jobService, InMemoryJobContext jobsConfiguration, String id, String timerId, String processInstanceId, boolean removeAtExecution,
+            Integer limit) {
+        this.id = id;
+        this.timerId = timerId;
+        this.processInstanceId = processInstanceId;
+        this.removeAtExecution = removeAtExecution;
+        this.limit = limit;
+        this.jobsConfiguration = jobsConfiguration;
+        this.jobService = jobService;
+    }
+
+    @Override
+    public void run() {
+        try {
+            Optional<Process<? extends Model>> process = jobsConfiguration.processes().processByProcessInstanceId(processInstanceId);
+            if (process.isEmpty()) {
+                LOGGER.info("Skipping Job {}. There is no process for pid {} ", id, processInstanceId);
+                return;
+            }
+            LOGGER.info("Job {} started", id);
+            limit--;
+            boolean executed = new TriggerJobCommand(processInstanceId, id, timerId, limit, process.get(), jobsConfiguration.unitOfWorkManager()).execute();
+            if (limit == 0 || !executed) {
+                jobService.cancelJob(id);
+            }
+            LOGGER.debug("Job {} completed", id);
+        } catch (ProcessInstanceOptimisticLockingException ex) {
+            LOGGER.info("Retrying Job {} due to: {}", id, ex.getMessage());
+            limit++;
+            run();
+        } finally {
+            if (removeAtExecution) {
+                jobService.cancelJob(id);
+            }
+        }
+    }
+}
+
+class StartProcessOnExpiredTimer implements Runnable {
+
+    private static Logger LOGGER = LoggerFactory.getLogger(StartProcessOnExpiredTimer.class);
+
+    private static final String TRIGGER = "timer";
+
+    private final String id;
+
+    private boolean removeAtExecution;
+    @SuppressWarnings("rawtypes")
+    private org.kie.kogito.process.Process process;
+
+    private Integer limit;
+
+    private JobsService jobService;
+    private InMemoryJobContext jobsConfiguration;
+
+    public StartProcessOnExpiredTimer(JobsService jobService, InMemoryJobContext jobsConfiguration, String id, org.kie.kogito.process.Process<?> process, boolean removeAtExecution, Integer limit) {
+        this.id = id;
+        this.process = process;
+        this.removeAtExecution = removeAtExecution;
+        this.limit = limit;
+        this.jobsConfiguration = jobsConfiguration;
+        this.jobService = jobService;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void run() {
+        try {
+            LOGGER.debug("Job {} started", id);
+            UnitOfWorkExecutor.executeInUnitOfWork(jobsConfiguration.unitOfWorkManager(), () -> {
+                org.kie.kogito.process.ProcessInstance<?> pi = process.createInstance(process.createModel());
+                if (pi != null) {
+                    pi.start(TRIGGER, null);
+                }
+                return null;
+            });
+            limit--;
+            if (limit == 0) {
+                jobService.cancelJob(id);
+            }
+            LOGGER.debug("Job {} completed", id);
+        } finally {
+            if (removeAtExecution) {
+                jobService.cancelJob(id);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryUserTaskJobExecutorFactory.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryUserTaskJobExecutorFactory.java
new file mode 100644
index 0000000000..1381928eba
--- /dev/null
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryUserTaskJobExecutorFactory.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.services.jobs.impl;
+
+import java.util.Optional;
+import java.util.Set;
+
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.jobs.descriptors.UserTaskInstanceJobDescription;
+import org.kie.kogito.process.ProcessInstanceOptimisticLockingException;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.kie.kogito.services.uow.UnitOfWorkExecutor.executeInUnitOfWork;
+
+public class InMemoryUserTaskJobExecutorFactory implements JobExecutorFactory {
+
+    private InMemoryJobContext jobsConfiguration;
+
+    public InMemoryUserTaskJobExecutorFactory(InMemoryJobContext jobsConfiguration) {
+        this.jobsConfiguration = jobsConfiguration;
+    }
+
+    @Override
+    public Set<Class<? extends JobDescription>> types() {
+        return Set.of(UserTaskInstanceJobDescription.class);
+    }
+
+    @Override
+    public Runnable createNewRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof UserTaskInstanceJobDescription userTaskInstanceJobDescription) {
+            return userTaskJobDescription(jobService, jobsConfiguration, userTaskInstanceJobDescription);
+        }
+        throw new IllegalArgumentException("single job description not supported for " + jobDescription);
+    }
+
+    @Override
+    public Runnable createNewRepeteableRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof UserTaskInstanceJobDescription userTaskInstanceJobDescription) {
+            return repetableUserTaskJobDescription(jobService, jobsConfiguration, userTaskInstanceJobDescription);
+        }
+        throw new IllegalArgumentException("repeteable job description not supported for " + jobDescription);
+    }
+
+    private Runnable userTaskJobDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, UserTaskInstanceJobDescription userTaskInstanceJobDescription) {
+        return new SignalUserTaskInstanceOnExpiredTimer(jobService, jobsConfiguration, userTaskInstanceJobDescription, true, -1);
+    }
+
+    private Runnable repetableUserTaskJobDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, UserTaskInstanceJobDescription userTaskInstanceJobDescription) {
+        return new SignalUserTaskInstanceOnExpiredTimer(jobService, jobsConfiguration, userTaskInstanceJobDescription, false, userTaskInstanceJobDescription.expirationTime().repeatLimit());
+    }
+
+}
+
+class SignalUserTaskInstanceOnExpiredTimer implements Runnable {
+
+    private static Logger LOGGER = LoggerFactory.getLogger(SignalUserTaskInstanceOnExpiredTimer.class);
+
+    private boolean removeAtExecution;
+    private Integer limit;
+    private JobsService jobService;
+
+    private InMemoryJobContext jobsConfiguration;
+
+    private UserTaskInstanceJobDescription userTaskInstanceJobDescription;
+
+    public SignalUserTaskInstanceOnExpiredTimer(JobsService jobService, InMemoryJobContext jobsConfiguration, UserTaskInstanceJobDescription userTaskInstanceJobDescription, boolean removeAtExecution,
+            Integer limit) {
+        this.userTaskInstanceJobDescription = userTaskInstanceJobDescription;
+        this.removeAtExecution = removeAtExecution;
+        this.limit = limit;
+        this.jobsConfiguration = jobsConfiguration;
+        this.jobService = jobService;
+    }
+
+    @Override
+    public void run() {
+        String jobId = userTaskInstanceJobDescription.id();
+        String userTaskInstanceId = userTaskInstanceJobDescription.getUserTaskInstanceId();
+        try {
+            Optional<UserTaskInstance> userTaskInstance = jobsConfiguration.userTasks().instances().findById(userTaskInstanceId);
+            if (userTaskInstance.isEmpty()) {
+                LOGGER.info("Skipping Job {}. There is no user task instance of id {} ", jobId, userTaskInstanceId);
+                return;
+            }
+            limit--;
+            executeInUnitOfWork(jobsConfiguration.unitOfWorkManager(), () -> {
+                userTaskInstance.get().trigger(userTaskInstanceJobDescription);
+                return null;
+            });
+            if (limit == 0) {
+                jobService.cancelJob(jobId);
+            }
+            LOGGER.debug("Job {} completed", jobId);
+        } catch (ProcessInstanceOptimisticLockingException ex) {
+            LOGGER.info("Retrying Job {} due to: {}", jobId, ex.getMessage());
+            limit++;
+            run();
+        } finally {
+            if (removeAtExecution) {
+                jobService.cancelJob(jobId);
+            }
+        }
+    }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/JobExecutorFactory.java
similarity index 60%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/JobExecutorFactory.java
index 6e85081ff7..f689e12577 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/JobExecutorFactory.java
@@ -16,20 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.kie.kogito.services.jobs.impl;
 
-package org.jbpm.usertask.jpa.repository;
+import java.util.Set;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.JobsService;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+public interface JobExecutorFactory {
 
-    public TaskInputRepository(UserTaskJPAContext context) {
-        super(context);
-    }
+    Set<Class<? extends JobDescription>> types();
 
-    @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    default boolean accept(JobDescription jobDescription) {
+        return types().contains(jobDescription.getClass());
     }
+
+    Runnable createNewRunnable(JobsService jobService, JobDescription jobDescription);
+
+    Runnable createNewRepeteableRunnable(JobsService jobService, JobDescription jobDescription);
+
 }
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobExecutorFactory.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobExecutorFactory.java
new file mode 100644
index 0000000000..0700b59528
--- /dev/null
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobExecutorFactory.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.services.jobs.impl;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.kie.api.runtime.process.ProcessInstance;
+import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessJobDescription;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.timer.TimerInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.kie.kogito.services.jobs.impl.TriggerJobCommand.SIGNAL;
+
+public class LegacyInMemoryJobExecutorFactory implements JobExecutorFactory {
+
+    private static final String TRIGGER = "timer";
+
+    private static Logger LOGGER = LoggerFactory.getLogger(LegacyInMemoryJobExecutorFactory.class);
+
+    private InMemoryJobContext jobsConfiguration;
+
+    public LegacyInMemoryJobExecutorFactory(InMemoryJobContext jobsConfiguration) {
+        this.jobsConfiguration = jobsConfiguration;
+    }
+
+    @Override
+    public Set<Class<? extends JobDescription>> types() {
+        return Set.of(ProcessInstanceJobDescription.class, ProcessJobDescription.class);
+    }
+
+    @Override
+    public Runnable createNewRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof ProcessInstanceJobDescription processInstanceJobDescription) {
+            return processInstanceJobDescription(jobService, jobsConfiguration, processInstanceJobDescription, true, 1);
+        } else if (jobDescription instanceof ProcessJobDescription processJobDescription) {
+            return processJobByDescription(jobService, jobsConfiguration, processJobDescription);
+        }
+        throw new IllegalArgumentException("single job description not supported for " + jobDescription);
+    }
+
+    @Override
+    public Runnable createNewRepeteableRunnable(JobsService jobService, JobDescription jobDescription) {
+        if (jobDescription instanceof ProcessInstanceJobDescription processInstanceJobDescription) {
+            return processInstanceJobDescription(jobService, jobsConfiguration, processInstanceJobDescription, false, processInstanceJobDescription.expirationTime().repeatLimit());
+        } else if (jobDescription instanceof ProcessJobDescription processJobDescription) {
+            return repeatableJobByDescription(jobService, jobsConfiguration, processJobDescription);
+        }
+        throw new IllegalArgumentException("multiple job description not supported for " + jobDescription);
+    }
+
+    protected Runnable processInstanceJobDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessInstanceJobDescription description, boolean remove, int limit) {
+        String id = description.id();
+        AtomicInteger counter = new AtomicInteger(limit);
+        return () -> {
+            try {
+                UnitOfWorkExecutor.executeInUnitOfWork(jobsConfiguration.unitOfWorkManager(), () -> {
+                    ProcessInstance pi = jobsConfiguration.runtime().getProcessInstance(description.processInstanceId());
+                    if (pi != null) {
+                        pi.signalEvent(SIGNAL, TimerInstance.with(description.id(), description.timerId(), counter.decrementAndGet()));
+                        if (counter.get() == 0) {
+                            jobService.cancelJob(id);
+                        }
+                    } else {
+                        // since owning process instance does not exist cancel timers
+                        jobService.cancelJob(id);
+                    }
+                    return null;
+                });
+                LOGGER.debug("Job {} completed", id);
+            } finally {
+                if (remove) {
+                    jobService.cancelJob(id);
+                }
+            }
+        };
+    }
+
+    protected Runnable processJobByDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessJobDescription description) {
+        return processCommand(jobService, jobsConfiguration, description, true);
+    }
+
+    protected Runnable repeatableJobByDescription(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessJobDescription description) {
+        return processCommand(jobService, jobsConfiguration, description, false);
+    }
+
+    private Runnable processCommand(JobsService jobService, InMemoryJobContext jobsConfiguration, ProcessJobDescription description, boolean remove) {
+
+        String id = description.id();
+        AtomicInteger counter = new AtomicInteger(description.expirationTime().repeatLimit());
+        String processId = description.processId();
+        return () -> {
+            try {
+                LOGGER.debug("Job {} started", id);
+                UnitOfWorkExecutor.executeInUnitOfWork(jobsConfiguration.unitOfWorkManager(), () -> {
+                    KogitoProcessInstance pi = jobsConfiguration.runtime().createProcessInstance(processId, null);
+                    if (pi != null) {
+                        jobsConfiguration.runtime().startProcessInstance(pi.getStringId(), TRIGGER);
+                    }
+                    return null;
+                });
+                if (counter.decrementAndGet() == 0) {
+                    jobService.cancelJob(id);
+                }
+                LOGGER.debug("Job {} completed", id);
+            } finally {
+                if (remove) {
+                    jobService.cancelJob(id);
+                }
+            }
+        };
+    }
+
+}
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobService.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobService.java
index 9ef2adcf86..960b96624b 100644
--- a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobService.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/LegacyInMemoryJobService.java
@@ -18,92 +18,13 @@
  */
 package org.kie.kogito.services.jobs.impl;
 
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.kie.api.runtime.process.ProcessInstance;
-import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
 import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
-import org.kie.kogito.services.uow.UnitOfWorkExecutor;
-import org.kie.kogito.timer.TimerInstance;
 import org.kie.kogito.uow.UnitOfWorkManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.kie.kogito.services.jobs.impl.TriggerJobCommand.SIGNAL;
 
 public class LegacyInMemoryJobService extends InMemoryJobService {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(LegacyInMemoryJobService.class);
-    private KogitoProcessRuntime processRuntime;
-
-    public LegacyInMemoryJobService(KogitoProcessRuntime processRuntime, UnitOfWorkManager unitOfWorkManager) {
-        super(null, unitOfWorkManager);
-        this.processRuntime = processRuntime;
-    }
-
-    @Override
-    public Runnable getSignalProcessInstanceCommand(ProcessInstanceJobDescription description, boolean remove, int limit) {
-        String id = description.id();
-        AtomicInteger counter = new AtomicInteger(limit);
-        return () -> {
-            try {
-                UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> {
-                    ProcessInstance pi = processRuntime.getProcessInstance(description.processInstanceId());
-                    if (pi != null) {
-                        pi.signalEvent(SIGNAL, TimerInstance.with(description.id(), description.timerId(), counter.decrementAndGet()));
-                        if (counter.get() == 0) {
-                            cancelJob(id, false);
-                        }
-                    } else {
-                        // since owning process instance does not exist cancel timers
-                        cancelJob(id, false);
-                    }
-                    return null;
-                });
-                LOGGER.debug("Job {} completed", id);
-            } finally {
-                if (remove) {
-                    cancelJob(id);
-                }
-            }
-        };
+    public LegacyInMemoryJobService(KogitoProcessRuntime runtime, UnitOfWorkManager unitOfWorkManager) {
+        registerJobExecutorFactory(new LegacyInMemoryJobExecutorFactory(new InMemoryJobContext(runtime, unitOfWorkManager, null, null)));
     }
 
-    @Override
-    protected Runnable processJobByDescription(ProcessJobDescription description) {
-        return processCommand(description, true);
-    }
-
-    private Runnable processCommand(ProcessJobDescription description, boolean remove) {
-        String id = description.id();
-        AtomicInteger counter = new AtomicInteger(description.expirationTime().repeatLimit());
-        String processId = description.processId();
-        return () -> {
-            try {
-                LOGGER.debug("Job {} started", id);
-                UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> {
-                    KogitoProcessInstance pi = processRuntime.createProcessInstance(processId, null);
-                    if (pi != null) {
-                        processRuntime.startProcessInstance(pi.getStringId(), TRIGGER);
-                    }
-                    return null;
-                });
-                if (counter.decrementAndGet() == 0) {
-                    cancelJob(id, false);
-                }
-                LOGGER.debug("Job {} completed", id);
-            } finally {
-                if (remove) {
-                    cancelJob(id);
-                }
-            }
-        };
-    }
-
-    @Override
-    protected Runnable repeatableProcessJobByDescription(ProcessJobDescription description) {
-        return processCommand(description, false);
-    }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/StaticJobService.java
similarity index 73%
copy from kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java
copy to api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/StaticJobService.java
index 64cea466d1..94c8027a34 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/StaticJobService.java
@@ -16,11 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package $Package$;
+package org.kie.kogito.services.jobs.impl;
 
-public class ApplicationConfig extends org.kie.kogito.StaticConfig {
+public class StaticJobService {
 
-    public ApplicationConfig() {
-        super($Addons$ /* additional values provided during codegen */);
+    private static InMemoryJobService INSTANCE;
+
+    public static InMemoryJobService staticJobService() {
+        if (INSTANCE == null) {
+            INSTANCE = new InMemoryJobService();
+        }
+        return INSTANCE;
     }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java b/api/kogito-services/src/main/java/org/kie/kogito/services/uow/StaticUnitOfWorkManger.java
similarity index 71%
copy from kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java
copy to api/kogito-services/src/main/java/org/kie/kogito/services/uow/StaticUnitOfWorkManger.java
index 3b5293ac0e..0d4b4b9665 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/uow/StaticUnitOfWorkManger.java
@@ -16,14 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.usertask.impl;
+package org.kie.kogito.services.uow;
 
-import org.kie.kogito.usertask.impl.DefaultUserTasks;
+import org.kie.kogito.uow.UnitOfWorkManager;
 
-public class UserTasks extends DefaultUserTasks {
+public class StaticUnitOfWorkManger {
 
-    public UserTasks(Application application) {
-        super(application);
-    }
+    private static final UnitOfWorkManager SINGLETON = new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory());
 
-}
\ No newline at end of file
+    public static UnitOfWorkManager staticUnitOfWorkManager() {
+        return SINGLETON;
+    }
+}
diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/uow/UnitOfWorkExecutor.java b/api/kogito-services/src/main/java/org/kie/kogito/services/uow/UnitOfWorkExecutor.java
index b9a81aea33..12f792fbee 100644
--- a/api/kogito-services/src/main/java/org/kie/kogito/services/uow/UnitOfWorkExecutor.java
+++ b/api/kogito-services/src/main/java/org/kie/kogito/services/uow/UnitOfWorkExecutor.java
@@ -43,7 +43,6 @@ public class UnitOfWorkExecutor {
             return result;
         } catch (ProcessInstanceExecutionException e) {
             uow.end();
-
             throw e;
         } catch (Exception e) {
             uow.abort();
diff --git a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/AbstractNodeHandler.java b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/AbstractNodeHandler.java
index 50eb0f0f9e..e0cea66062 100755
--- a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/AbstractNodeHandler.java
+++ b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/AbstractNodeHandler.java
@@ -634,6 +634,10 @@ public abstract class AbstractNodeHandler extends BaseAbstractHandler implements
     }
 
     private String cleanUp(String expression) {
+        // this is for user task expressions (they should be set as it is)
+        if (expression.startsWith("[")) {
+            return expression;
+        }
         Matcher matcher = PatternConstants.PARAMETER_MATCHER.matcher(expression);
         String temp = expression;
         if (matcher.find()) {
diff --git a/jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/BpmnProcess.java b/jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/BpmnProcess.java
index df188e2b2e..bb61669e9b 100644
--- a/jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/BpmnProcess.java
+++ b/jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/BpmnProcess.java
@@ -37,8 +37,8 @@ import org.kie.kogito.process.impl.AbstractProcess;
 import org.kie.kogito.process.impl.DefaultProcessEventListenerConfig;
 import org.kie.kogito.process.impl.DefaultWorkItemHandlerConfig;
 import org.kie.kogito.process.impl.StaticProcessConfig;
-import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
-import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
+
+import static org.kie.kogito.services.uow.StaticUnitOfWorkManger.staticUnitOfWorkManager;
 
 public class BpmnProcess extends AbstractProcess<BpmnVariables> {
 
@@ -127,7 +127,7 @@ public class BpmnProcess extends AbstractProcess<BpmnVariables> {
     }
 
     public static List<BpmnProcess> from(WorkItemHandlerConfig config, Resource... resource) {
-        return from(new StaticProcessConfig(config, new DefaultProcessEventListenerConfig(), new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory())), resource);
+        return from(new StaticProcessConfig(config, new DefaultProcessEventListenerConfig(), staticUnitOfWorkManager()), resource);
     }
 
     public static List<BpmnProcess> from(ProcessConfig config, Resource... resources) {
diff --git a/jbpm/jbpm-flow/pom.xml b/jbpm/jbpm-flow/pom.xml
index d0fc15edb2..0da528e8e2 100755
--- a/jbpm/jbpm-flow/pom.xml
+++ b/jbpm/jbpm-flow/pom.xml
@@ -152,6 +152,11 @@
       <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.awaitility</groupId>
+      <artifactId>awaitility</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <profiles>
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/AbstractProcessRuntime.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/AbstractProcessRuntime.java
index cb388bb361..2223cd5687 100644
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/AbstractProcessRuntime.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/AbstractProcessRuntime.java
@@ -42,7 +42,7 @@ import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessJobDescription;
 import org.kie.kogito.signal.SignalManager;
 
 import static org.jbpm.process.core.constants.CalendarConstants.BUSINESS_CALENDAR_ENVIRONMENT_KEY;
@@ -100,7 +100,7 @@ public abstract class AbstractProcessRuntime implements InternalProcessRuntime {
             if (startNodes != null && !startNodes.isEmpty()) {
                 for (StartNode startNode : startNodes) {
                     if (startNode != null && startNode.getTimer() != null) {
-                        jobService.scheduleProcessJob(ProcessJobDescription.of(createTimerInstance(startNode.getTimer(), kruntime), p.getId()));
+                        jobService.scheduleJob(ProcessJobDescription.of(createTimerInstance(startNode.getTimer(), kruntime), p.getId()));
                     }
                 }
             }
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightProcessRuntime.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightProcessRuntime.java
index a972d72daf..7c1a43e5c8 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightProcessRuntime.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightProcessRuntime.java
@@ -51,8 +51,6 @@ import org.kie.kogito.Application;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.process.Processes;
-import org.kie.kogito.services.jobs.impl.InMemoryJobService;
 import org.kie.kogito.signal.SignalManager;
 import org.kie.kogito.uow.UnitOfWorkManager;
 
@@ -81,7 +79,7 @@ public class LightProcessRuntime extends AbstractProcessRuntime {
         this.runtimeContext = runtimeContext;
         this.processInstanceManager = services.getProcessInstanceManager();
         this.signalManager = services.getSignalManager();
-        this.jobService = services.getJobsService() == null ? InMemoryJobService.get(application.get(Processes.class), this.unitOfWorkManager) : services.getJobsService();
+        this.jobService = services.getJobsService();
         this.processEventSupport = services.getEventSupport();
         this.workItemManager = services.getKogitoWorkItemManager();
         if (isActive()) {
@@ -144,9 +142,7 @@ public class LightProcessRuntime extends AbstractProcessRuntime {
     @Override
     public KogitoProcessInstance createProcessInstance(String processId, CorrelationKey correlationKey, Map<String, Object> parameters) {
         return createProcessInstance(
-                runtimeContext.findProcess(processId)
-                        .orElseThrow(() -> new IllegalArgumentException(
-                                "Unknown process ID: " + processId)),
+                runtimeContext.findProcess(processId).orElseThrow(() -> new IllegalArgumentException("Unknown process ID: " + processId)),
                 correlationKey, parameters);
     }
 
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/AsyncEventNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/AsyncEventNodeInstance.java
index f96f9476ad..59cc4263c8 100644
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/AsyncEventNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/AsyncEventNodeInstance.java
@@ -33,7 +33,7 @@ import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.process.ProcessInstance;
 import org.kie.kogito.services.uow.BaseWorkUnit;
 import org.kie.kogito.timer.TimerInstance;
@@ -99,7 +99,7 @@ public class AsyncEventNodeInstance extends EventNodeInstance {
                 new BaseWorkUnit<>(this, instance -> {
                     ExpirationTime expirationTime = ExactExpirationTime.of(ZonedDateTime.now().plus(1, ChronoUnit.MILLIS));
                     ProcessInstanceJobDescription jobDescription =
-                            ProcessInstanceJobDescription.builder()
+                            ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                                     .id(getJobId())
                                     .timerId("-1")
                                     .expirationTime(expirationTime)
@@ -110,7 +110,7 @@ public class AsyncEventNodeInstance extends EventNodeInstance {
                                     .nodeInstanceId(Optional.ofNullable(from).map(KogitoNodeInstance::getStringId).orElse(null))
                                     .build();
                     JobsService jobService = processRuntime.getJobsService();
-                    String jobId = jobService.scheduleProcessInstanceJob(jobDescription);
+                    String jobId = jobService.scheduleJob(jobDescription);
                     setJobId(jobId);
                 }, i -> {
                 }, WorkUnit.LOW_PRIORITY));
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java
index 872276850a..1632732093 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java
@@ -38,9 +38,9 @@ public class HumanTaskNode extends WorkItemNode {
             "TaskName",
             "NodeName",
             "NotStartedNotify",
+            "NotStartedReassign",
             "NotCompletedNotify",
             "NotCompletedReassign",
-            "NotStartedReassign",
             "Description",
             "Comment",
             "ActorId",
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
index 27a67f296e..aa68a09c33 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
@@ -95,7 +95,7 @@ import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess;
 import org.kie.kogito.internal.process.runtime.MessageException;
 import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.process.BaseEventDescription;
 import org.kie.kogito.process.EventDescription;
 import org.kie.kogito.process.NamedDataType;
@@ -599,7 +599,7 @@ public abstract class WorkflowProcessInstanceImpl extends ProcessInstanceImpl im
 
     private TimerInstance registerTimer(TimerInstance timerInstance) {
         ProcessInstanceJobDescription description =
-                ProcessInstanceJobDescription.builder()
+                ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                         .id(timerInstance.getId())
                         .timerId(timerInstance.getTimerId())
                         .expirationTime(DurationExpirationTime.after(timerInstance.getDelay()))
@@ -607,7 +607,7 @@ public abstract class WorkflowProcessInstanceImpl extends ProcessInstanceImpl im
                         .processId(getProcessId())
                         .build();
         JobsService jobsService = InternalProcessRuntime.asKogitoProcessRuntime(getKnowledgeRuntime().getProcessRuntime()).getJobsService();
-        jobsService.scheduleProcessInstanceJob(description);
+        jobsService.scheduleJob(description);
         return timerInstance;
     }
 
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
index 2b0517718c..c03ce5c437 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
@@ -52,7 +52,7 @@ import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.process.expr.Expression;
 import org.kie.kogito.process.expr.ExpressionHandlerFactory;
 import org.kie.kogito.timer.TimerInstance;
@@ -95,7 +95,7 @@ public abstract class StateBasedNodeInstance extends ExtendedNodeInstanceImpl im
             JobsService jobService = ((KogitoProcessRuntime.Provider) getProcessInstance().getKnowledgeRuntime().getProcessRuntime()).getKogitoProcessRuntime().getJobsService();
             for (Timer timer : timers.keySet()) {
                 ProcessInstanceJobDescription jobDescription =
-                        ProcessInstanceJobDescription.builder()
+                        ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                                 .generateId()
                                 .timerId(Long.toString(timer.getId()))
                                 .expirationTime(createTimerInstance(timer))
@@ -105,7 +105,7 @@ public abstract class StateBasedNodeInstance extends ExtendedNodeInstanceImpl im
                                 .processInstanceId(getProcessInstance().getStringId())
                                 .nodeInstanceId(this.getId())
                                 .build();
-                String jobId = jobService.scheduleProcessInstanceJob(jobDescription);
+                String jobId = jobService.scheduleJob(jobDescription);
                 timerInstances.add(jobId);
                 timerInstancesReference.put(jobId, Long.toString(timer.getId()));
             }
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java
index 452e978528..5ca0c76a28 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java
@@ -36,7 +36,7 @@ import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
 import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.process.BaseEventDescription;
 import org.kie.kogito.process.EventDescription;
 import org.kie.kogito.services.uow.BaseWorkUnit;
@@ -82,7 +82,7 @@ public class TimerNodeInstance extends StateBasedNodeInstance implements EventLi
         processRuntime.getUnitOfWorkManager().currentUnitOfWork().intercept(
                 new BaseWorkUnit<>(this, instance -> {
                     ProcessInstanceJobDescription jobDescription =
-                            ProcessInstanceJobDescription.builder()
+                            ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                                     .id(getTimerId())
                                     .timerId("-1")
                                     .expirationTime(expirationTime)
@@ -93,7 +93,7 @@ public class TimerNodeInstance extends StateBasedNodeInstance implements EventLi
                                     .nodeInstanceId(this.getId())
                                     .build();
                     JobsService jobService = processRuntime.getJobsService();
-                    String jobId = jobService.scheduleProcessInstanceJob(jobDescription);
+                    String jobId = jobService.scheduleJob(jobDescription);
                     internalSetTimerId(jobId);
                 }, i -> {
                 }, WorkUnit.LOW_PRIORITY));
diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcess.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcess.java
index 96ec9a0e65..89e3193c79 100644
--- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcess.java
+++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcess.java
@@ -57,7 +57,7 @@ import org.kie.kogito.internal.utils.ConversionUtils;
 import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
-import org.kie.kogito.jobs.ProcessJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessJobDescription;
 import org.kie.kogito.process.MutableProcessInstances;
 import org.kie.kogito.process.Process;
 import org.kie.kogito.process.ProcessConfig;
@@ -227,7 +227,7 @@ public abstract class AbstractProcess<T extends Model> implements Process<T>, Pr
         if (startNodes != null && !startNodes.isEmpty()) {
             for (StartNode startNode : startNodes) {
                 if (startNode != null && startNode.getTimer() != null) {
-                    String timerId = processRuntime.getJobsService().scheduleProcessJob(ProcessJobDescription.of(configureTimerInstance(startNode.getTimer()), this));
+                    String timerId = processRuntime.getJobsService().scheduleJob(ProcessJobDescription.of(configureTimerInstance(startNode.getTimer()), this));
                     startTimerInstances.add(timerId);
                 }
             }
diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessConfig.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessConfig.java
index ac13e65942..826efc66d4 100644
--- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessConfig.java
+++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessConfig.java
@@ -36,6 +36,7 @@ import org.kie.kogito.process.ProcessEventListenerConfig;
 import org.kie.kogito.process.ProcessVersionResolver;
 import org.kie.kogito.process.WorkItemHandlerConfig;
 import org.kie.kogito.services.identity.NoOpIdentityProvider;
+import org.kie.kogito.services.jobs.impl.StaticJobService;
 import org.kie.kogito.services.signal.DefaultSignalManagerHub;
 import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
 import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
@@ -69,10 +70,8 @@ public abstract class AbstractProcessConfig implements ProcessConfig {
 
         this.workItemHandlerConfig = mergeWorkItemHandler(workItemHandlerConfig, DefaultWorkItemHandlerConfig::new);
         this.processEventListenerConfig = merge(processEventListenerConfigs, processEventListeners);
-        this.unitOfWorkManager = orDefault(unitOfWorkManager,
-                () -> new DefaultUnitOfWorkManager(
-                        new CollectingUnitOfWorkFactory()));
-        this.jobsService = orDefault(jobsService, () -> null);
+        this.unitOfWorkManager = orDefault(unitOfWorkManager, () -> new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()));
+        this.jobsService = orDefault(jobsService, StaticJobService::staticJobService);
         this.versionResolver = orDefault(versionResolver, () -> null);
         this.identityProvider = orDefault(identityProvider, NoOpIdentityProvider::new);
         this.businessCalendar = orDefault(businessCalendar, () -> null);
diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/StaticProcessConfig.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/StaticProcessConfig.java
index 343f232a04..55f688757b 100644
--- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/StaticProcessConfig.java
+++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/StaticProcessConfig.java
@@ -27,11 +27,12 @@ import org.kie.kogito.process.ProcessVersionResolver;
 import org.kie.kogito.process.WorkItemHandlerConfig;
 import org.kie.kogito.services.identity.NoOpIdentityProvider;
 import org.kie.kogito.services.signal.DefaultSignalManagerHub;
-import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
-import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
 import org.kie.kogito.signal.SignalManagerHub;
 import org.kie.kogito.uow.UnitOfWorkManager;
 
+import static org.kie.kogito.services.jobs.impl.StaticJobService.staticJobService;
+import static org.kie.kogito.services.uow.StaticUnitOfWorkManger.staticUnitOfWorkManager;
+
 public class StaticProcessConfig implements ProcessConfig {
 
     private final WorkItemHandlerConfig workItemHandlerConfig;
@@ -44,11 +45,21 @@ public class StaticProcessConfig implements ProcessConfig {
     private final IdentityProvider identityProvider;
     private final BusinessCalendar businessCalendar;
 
+    public StaticProcessConfig(JobsService jobService) {
+        this(new DefaultWorkItemHandlerConfig(),
+                new DefaultProcessEventListenerConfig(),
+                staticUnitOfWorkManager(),
+                jobService,
+                null,
+                new NoOpIdentityProvider(),
+                null);
+    }
+
     public StaticProcessConfig(
             WorkItemHandlerConfig workItemHandlerConfig,
             ProcessEventListenerConfig processEventListenerConfig,
             UnitOfWorkManager unitOfWorkManager) {
-        this(workItemHandlerConfig, processEventListenerConfig, unitOfWorkManager, null, null, new NoOpIdentityProvider(), null);
+        this(workItemHandlerConfig, processEventListenerConfig, unitOfWorkManager, staticJobService(), null, new NoOpIdentityProvider(), null);
     }
 
     public StaticProcessConfig(
@@ -72,8 +83,8 @@ public class StaticProcessConfig implements ProcessConfig {
     public StaticProcessConfig() {
         this(new DefaultWorkItemHandlerConfig(),
                 new DefaultProcessEventListenerConfig(),
-                new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()),
-                null,
+                staticUnitOfWorkManager(),
+                staticJobService(),
                 null,
                 new NoOpIdentityProvider(),
                 null);
diff --git a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/TimerTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/TimerTest.java
index 67f835b37a..93f0279d81 100755
--- a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/TimerTest.java
+++ b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/TimerTest.java
@@ -18,6 +18,7 @@
  */
 package org.jbpm.process;
 
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
 import org.drools.core.common.InternalWorkingMemory;
@@ -26,21 +27,19 @@ import org.jbpm.process.instance.InternalProcessRuntime;
 import org.jbpm.process.instance.ProcessRuntimeFactoryServiceImpl;
 import org.jbpm.ruleflow.instance.RuleFlowProcessInstance;
 import org.jbpm.test.util.AbstractBaseTest;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
 import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.ExactExpirationTime;
-import org.kie.kogito.jobs.JobsService;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.services.jobs.impl.LegacyInMemoryJobService;
-import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
-import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
+import org.kie.kogito.services.uow.StaticUnitOfWorkManger;
 import org.kie.kogito.timer.TimerInstance;
 import org.slf4j.LoggerFactory;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
 import static org.jbpm.workflow.instance.node.TimerNodeInstance.TIMER_TRIGGERED_EVENT;
 
 public class TimerTest extends AbstractBaseTest {
@@ -57,95 +56,75 @@ public class TimerTest extends AbstractBaseTest {
 
     @Test
     @Timeout(value = 10L, unit = TimeUnit.SECONDS)
-    @Disabled
-    public void testTimer() {
+    public void testTimer() throws Exception {
         KogitoProcessRuntime kruntime = createKogitoProcessRuntime();
-
-        RuleFlowProcessInstance processInstance = new RuleFlowProcessInstance() {
-            private static final long serialVersionUID = 510l;
-
-            public void signalEvent(String type, Object event) {
-                if (TIMER_TRIGGERED_EVENT.equals(type)) {
-                    TimerInstance timer = (TimerInstance) event;
-                    logger.info("Timer {} triggered", timer.getId());
-                    counter++;
+        try (LegacyInMemoryJobService jobService = new LegacyInMemoryJobService(kruntime, StaticUnitOfWorkManger.staticUnitOfWorkManager());) {
+            RuleFlowProcessInstance processInstance = new RuleFlowProcessInstance() {
+                private static final long serialVersionUID = 510l;
+
+                public void signalEvent(String type, Object event) {
+                    if (TIMER_TRIGGERED_EVENT.equals(type)) {
+                        TimerInstance timer = (TimerInstance) event;
+                        logger.info("Timer {} triggered", timer.getId());
+                        counter++;
+                    }
                 }
-            }
-        };
-        processInstance.setKnowledgeRuntime(((InternalWorkingMemory) kruntime.getKieSession()).getKnowledgeRuntime());
-        processInstance.setId("1234");
-        InternalProcessRuntime processRuntime = ((InternalProcessRuntime) ((InternalWorkingMemory) kruntime.getKieSession()).getProcessRuntime());
-        processRuntime.getProcessInstanceManager().internalAddProcessInstance(processInstance);
-
-        new Thread(() -> kruntime.getKieSession().fireUntilHalt()).start();
-        JobsService jobService = new LegacyInMemoryJobService(kruntime, new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()));
-
-        ProcessInstanceJobDescription desc = ProcessInstanceJobDescription.builder()
-                .expirationTime(ExactExpirationTime.now())
-                .processInstanceId(processInstance.getStringId())
-                .processId("test")
-                .id("job1")
-                .timerId("timer1")
-                .build();
-        jobService.scheduleProcessInstanceJob(desc);
-
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            // do nothing
-        }
-        assertThat(counter).isEqualTo(1);
-
-        counter = 0;
-        desc = ProcessInstanceJobDescription.builder()
-                .expirationTime(DurationExpirationTime.after(500))
-                .processInstanceId(processInstance.getStringId())
-                .processId("test")
-                .id("job2")
-                .timerId("timer2")
-                .build();
-        jobService.scheduleProcessInstanceJob(desc);
-        assertThat(counter).isZero();
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            // do nothing
-        }
-        assertThat(counter).isEqualTo(1);
-
-        counter = 0;
-        desc = ProcessInstanceJobDescription.builder()
-                .expirationTime(DurationExpirationTime.repeat(500, 300L))
-                .processInstanceId(processInstance.getStringId())
-                .processId("test")
-                .id("job3")
-                .timerId("timer3")
-                .build();
-        String jobId = jobService.scheduleProcessInstanceJob(desc);
-        assertThat(counter).isZero();
-        try {
-            Thread.sleep(700);
-        } catch (InterruptedException e) {
-            // do nothing
-        }
-        assertThat(counter).isEqualTo(1);
+            };
+            processInstance.setKnowledgeRuntime(((InternalWorkingMemory) kruntime.getKieSession()).getKnowledgeRuntime());
+            processInstance.setId("1234");
+            InternalProcessRuntime processRuntime = ((InternalProcessRuntime) ((InternalWorkingMemory) kruntime.getKieSession()).getProcessRuntime());
+            processRuntime.getProcessInstanceManager().internalAddProcessInstance(processInstance);
+
+            new Thread(() -> kruntime.getKieSession().fireUntilHalt()).start();
+
+            ProcessInstanceJobDescription desc = ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
+                    .expirationTime(ExactExpirationTime.now())
+                    .processInstanceId(processInstance.getStringId())
+                    .processId("test")
+                    .id("job1")
+                    .timerId("timer1")
+                    .build();
+            jobService.scheduleJob(desc);
+
+            await().atMost(Duration.ofSeconds(5L)).until(() -> counter == 1);
+            assertThat(counter).isEqualTo(1);
+
+            counter = 0;
+            desc = ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
+                    .expirationTime(DurationExpirationTime.after(500))
+                    .processInstanceId(processInstance.getStringId())
+                    .processId("test")
+                    .id("job2")
+                    .timerId("timer2")
+                    .build();
+            jobService.scheduleJob(desc);
+            assertThat(counter).isZero();
+            await().atMost(Duration.ofSeconds(5L)).until(() -> counter == 1);
+            assertThat(counter).isEqualTo(1);
+
+            counter = 0;
+            desc = ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
+                    .expirationTime(DurationExpirationTime.repeat(500, 300L))
+                    .processInstanceId(processInstance.getStringId())
+                    .processId("test")
+                    .id("job3")
+                    .timerId("timer3")
+                    .build();
+            String jobId = jobService.scheduleJob(desc);
+            assertThat(counter).isZero();
+
+            await().atMost(Duration.ofSeconds(5L)).until(() -> counter == 1);
+            assertThat(counter).isEqualTo(1);
+
+            await().atMost(Duration.ofSeconds(5L)).until(() -> counter == 4);
+            assertThat(counter).isEqualTo(4);
+
+            jobService.cancelJob(jobId);
+            int lastCount = counter;
+            // value is preserved
+            await().during(Duration.ofSeconds(2L)).until(() -> counter == lastCount);
 
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            // do nothing
-        }
-        // we can't know exactly how many times this will fire as timers are not precise, but should be at least 4
-        assertThat(counter >= 4).isTrue();
-
-        jobService.cancelJob(jobId);
-        int lastCount = counter;
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            // do nothing
         }
-        assertThat(counter).isEqualTo(lastCount);
     }
 
 }
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java
index c89cc7a824..0ca0a9bde8 100644
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java
@@ -32,12 +32,8 @@ import org.jbpm.test.utils.ProcessTestHelper;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.Application;
-import org.kie.kogito.StaticApplication;
-import org.kie.kogito.StaticConfig;
 import org.kie.kogito.calendar.BusinessCalendar;
-import org.kie.kogito.process.ProcessConfig;
 import org.kie.kogito.process.ProcessInstance;
-import org.kie.kogito.process.bpmn2.BpmnProcesses;
 import org.kie.kogito.process.impl.AbstractProcessConfig;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -55,9 +51,7 @@ public class BusinessCalendarTest {
 
     @Test
     public void testTimerWithWorkingDayCalendar() throws InterruptedException {
-        BpmnProcesses bpmnProcesses = new BpmnProcesses();
-        ProcessConfig config = new MockProcessConfig(workingDayCalendar);
-        Application app = new StaticApplication(new StaticConfig(null, config), bpmnProcesses);
+        Application app = ProcessTestHelper.newApplication(new MockProcessConfig(workingDayCalendar));
         TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
         ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
         org.kie.kogito.process.Process<BusinessCalendarTimerModel> processDefinition = BusinessCalendarTimerProcess.newProcess(app);
@@ -71,9 +65,7 @@ public class BusinessCalendarTest {
 
     @Test
     public void testTimerWithNotWorkingDayCalendar() throws InterruptedException {
-        BpmnProcesses bpmnProcesses = new BpmnProcesses();
-        ProcessConfig config = new MockProcessConfig(notWorkingDayCalendar);
-        Application app = new StaticApplication(new StaticConfig(null, config), bpmnProcesses);
+        Application app = ProcessTestHelper.newApplication(new MockProcessConfig(notWorkingDayCalendar));
         TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
         ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
         org.kie.kogito.process.Process<BusinessCalendarTimerModel> processDefinition = BusinessCalendarTimerProcess.newProcess(app);
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/test/utils/ProcessTestHelper.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/test/utils/ProcessTestHelper.java
index 1c111d88d9..9da2d64e30 100644
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/test/utils/ProcessTestHelper.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/test/utils/ProcessTestHelper.java
@@ -30,6 +30,7 @@ import org.jbpm.bpmn2.intermediate.IntermediateCatchEventTimerDurationWithErrorP
 import org.kie.api.event.process.ProcessNodeEvent;
 import org.kie.api.event.process.ProcessNodeLeftEvent;
 import org.kie.api.event.process.ProcessNodeTriggeredEvent;
+import org.kie.kogito.Addons;
 import org.kie.kogito.Application;
 import org.kie.kogito.Model;
 import org.kie.kogito.StaticApplication;
@@ -45,22 +46,29 @@ import org.kie.kogito.process.bpmn2.BpmnProcesses;
 import org.kie.kogito.process.impl.DefaultProcessEventListenerConfig;
 import org.kie.kogito.process.impl.DefaultWorkItemHandlerConfig;
 import org.kie.kogito.process.impl.StaticProcessConfig;
-import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
-import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static java.util.Collections.emptyList;
+import static org.kie.kogito.services.jobs.impl.StaticJobService.staticJobService;
+import static org.kie.kogito.services.uow.StaticUnitOfWorkManger.staticUnitOfWorkManager;
 
 public class ProcessTestHelper {
 
     private static Logger LOGGER = LoggerFactory.getLogger(ProcessTestHelper.class);
 
     public static Application newApplication() {
+        return newApplication(new StaticProcessConfig());
+    }
+
+    public static Application newApplication(ProcessConfig staticConfig) {
         BpmnProcesses bpmnProcesses = new BpmnProcesses();
-        StaticProcessConfig staticConfig =
-                new StaticProcessConfig(new DefaultWorkItemHandlerConfig(), new DefaultProcessEventListenerConfig(), new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()));
-        return new StaticApplication(new StaticConfig(null, staticConfig), bpmnProcesses);
+        InMemoryJobContext context = new InMemoryJobContext(null, staticUnitOfWorkManager(), bpmnProcesses, null);
+        staticJobService().clearJobExecutorFactories();
+        staticJobService().registerJobExecutorFactory(new InMemoryProcessJobExecutorFactory(context));
+        return new StaticApplication(new StaticConfig(Addons.EMTPY, staticConfig), bpmnProcesses);
     }
 
     public static void registerProcessEventListener(Application app, KogitoProcessEventListener kogitoProcessEventListener) {
diff --git a/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
index cf30b7ed2b..000e78a2d2 100644
--- a/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
+++ b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
@@ -35,6 +35,7 @@ import org.kie.kogito.usertask.UserTask;
 import org.kie.kogito.usertask.UserTasks;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle;
+import org.kie.kogito.usertask.impl.model.DeadlineHelper;
 
 import static java.util.Collections.emptyMap;
 import static java.util.Optional.ofNullable;
@@ -51,12 +52,18 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
     private static final String DESCRIPTION = "Description";
     private static final String PRIORITY = "Priority";
     private static final String TASK_NAME = "TaskName";
+    private static final String NODE_NAME = "NodeName";
     private static final String ACTOR_ID = "ActorId";
     private static final String GROUP_ID = "GroupId";
     private static final String BUSINESSADMINISTRATOR_ID = "BusinessAdministratorId";
     private static final String BUSINESSADMINISTRATOR_GROUP_ID = "BusinessAdministratorGroupId";
     private static final String EXCLUDED_OWNER_ID = "ExcludedOwnerId";
 
+    private static final String NOT_STARTED_NOTIFY = "NotStartedNotify";
+    private static final String NOT_STARTED_REASSIGN = "NotStartedReassign";
+    private static final String NOT_COMPLETED_NOTIFY = "NotCompletedNotify";
+    private static final String NOT_COMPLETED_REASSIGN = "NotCompletedReassign";
+
     public UserTaskKogitoWorkItemHandler() {
         super();
     }
@@ -78,7 +85,7 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
 
         userTask.instances().create(instance);
 
-        instance.setTaskName((String) workItem.getParameter(TASK_NAME));
+        instance.setTaskName(ofNullable((String) workItem.getParameter(TASK_NAME)).orElse((String) workItem.getParameter(NODE_NAME)));
         instance.setTaskDescription((String) workItem.getParameter(DESCRIPTION));
         instance.setTaskPriority(priority != null ? priority.toString() : null);
 
@@ -100,7 +107,12 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
         ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminGroups);
         ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setExcludedUsers);
 
-        instance.transition(DefaultUserTaskLifeCycle.ACTIVATE, emptyMap(), IdentityProviders.of(WORKFLOW_ENGINE_USER));
+        ofNullable(workItem.getParameters().get(NOT_STARTED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(instance::setNotStartedDeadlines);
+        ofNullable(workItem.getParameters().get(NOT_STARTED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(instance::setNotStartedReassignments);
+        ofNullable(workItem.getParameters().get(NOT_COMPLETED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(instance::setNotCompletedDeadlines);
+        ofNullable(workItem.getParameters().get(NOT_COMPLETED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(instance::setNotCompletedReassignments);
+
+        instance.initialize(emptyMap(), IdentityProviders.of(WORKFLOW_ENGINE_USER));
 
         if (workItem instanceof InternalKogitoWorkItem ikw) {
             ikw.setExternalReferenceId(instance.getId());
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
index 795c0f25e9..8fb214c0b1 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
@@ -20,13 +20,13 @@ package org.kie.kogito.usertask.impl;
 
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 
 import org.kie.kogito.usertask.UserTask;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.impl.model.DeadlineHelper;
 import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
 import org.kie.kogito.usertask.model.Reassignment;
 
 public abstract class AbstractUserTask implements UserTask {
@@ -44,10 +44,10 @@ public abstract class AbstractUserTask implements UserTask {
     private Set<String> adminUsers;
     private Set<String> adminGroups;
     private Set<String> excludedUsers;
-    private Collection<DeadlineInfo<Map<String, Object>>> startDeadlines;
-    private Collection<DeadlineInfo<Map<String, Object>>> endDeadlines;
+    private Collection<DeadlineInfo<Notification>> startDeadlines;
+    private Collection<DeadlineInfo<Notification>> endDeadlines;
     private Collection<DeadlineInfo<Reassignment>> startReassigments;
-    private Collection<DeadlineInfo<Reassignment>> endReassigments;
+    private Collection<DeadlineInfo<Reassignment>> endReassignments;
 
     public AbstractUserTask(String id, String name) {
         this.id = id;
@@ -61,7 +61,7 @@ public abstract class AbstractUserTask implements UserTask {
         this.startDeadlines = new HashSet<>();
         this.endDeadlines = new HashSet<>();
         this.startReassigments = new HashSet<>();
-        this.endReassigments = new HashSet<>();
+        this.endReassignments = new HashSet<>();
     }
 
     @Override
@@ -77,6 +77,10 @@ public abstract class AbstractUserTask implements UserTask {
         instance.setAdminGroups(getAdminGroups());
         instance.setExcludedUsers(getExcludedUsers());
         instance.setInstances(this.instances());
+        instance.setNotCompletedDeadlines(this.getNotCompletedDeadlines());
+        instance.setNotCompletedReassignments(this.getNotCompletedReassignments());
+        instance.setNotStartedDeadlines(this.getNotStartedDeadlines());
+        instance.setNotStartedReassignments(this.getNotStartedReassignments());
         return instance;
     }
 
@@ -200,7 +204,7 @@ public abstract class AbstractUserTask implements UserTask {
     }
 
     @Override
-    public Collection<DeadlineInfo<Map<String, Object>>> getNotStartedDeadlines() {
+    public Collection<DeadlineInfo<Notification>> getNotStartedDeadlines() {
         return startDeadlines;
     }
 
@@ -209,7 +213,7 @@ public abstract class AbstractUserTask implements UserTask {
     }
 
     @Override
-    public Collection<DeadlineInfo<Map<String, Object>>> getNotCompletedDeadlines() {
+    public Collection<DeadlineInfo<Notification>> getNotCompletedDeadlines() {
         return endDeadlines;
     }
 
@@ -227,12 +231,12 @@ public abstract class AbstractUserTask implements UserTask {
     }
 
     @Override
-    public Collection<DeadlineInfo<Reassignment>> getNotCompletedReassigments() {
-        return endReassigments;
+    public Collection<DeadlineInfo<Reassignment>> getNotCompletedReassignments() {
+        return endReassignments;
     }
 
     public void setNotCompletedReassigments(String reassignments) {
-        this.endReassigments = DeadlineHelper.parseReassignments(reassignments);
+        this.endReassignments = DeadlineHelper.parseReassignments(reassignments);
     }
 
     protected Set<String> toSet(String value) {
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java
index 7282ce7253..88ce68e6b9 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java
@@ -24,8 +24,10 @@ import java.util.function.Supplier;
 import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.jobs.JobsService;
 import org.kie.kogito.services.identity.NoOpIdentityProvider;
+import org.kie.kogito.services.jobs.impl.StaticJobService;
 import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
 import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
+import org.kie.kogito.services.uow.StaticUnitOfWorkManger;
 import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
 import org.kie.kogito.usertask.UserTaskConfig;
@@ -33,9 +35,15 @@ import org.kie.kogito.usertask.UserTaskEventListenerConfig;
 import org.kie.kogito.usertask.UserTaskInstances;
 import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.kie.kogito.services.jobs.impl.StaticJobService.staticJobService;
 
 public class DefaultUserTaskConfig implements UserTaskConfig {
 
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultUserTaskConfig.class);
+
     private UserTaskEventListenerConfig userTaskEventListeners;
     private UnitOfWorkManager unitOfWorkManager;
     private JobsService jobService;
@@ -47,7 +55,7 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
     public DefaultUserTaskConfig() {
         this(new DefaultUserTaskEventListenerConfig(),
                 new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()),
-                null,
+                staticJobService(),
                 new NoOpIdentityProvider(),
                 new DefaultUserTaskLifeCycle(),
                 new DefaultUserTaskAssignmentStrategyConfig(),
@@ -64,8 +72,8 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
             Iterable<UserTaskInstances> userTaskInstances) {
 
         this.userTaskEventListeners = singleton(userTaskEventListenerConfig, DefaultUserTaskEventListenerConfig::new);
-        this.unitOfWorkManager = singleton(unitOfWorkManager, () -> new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()));
-        this.jobService = singleton(jobService, () -> null);
+        this.unitOfWorkManager = singleton(unitOfWorkManager, StaticUnitOfWorkManger::staticUnitOfWorkManager);
+        this.jobService = singleton(jobService, StaticJobService::staticJobService);
         this.identityProvider = singleton(identityProvider, NoOpIdentityProvider::new);
         this.userTaskLifeCycle = singleton(userTaskLifeCycle, DefaultUserTaskLifeCycle::new);
         this.userTaskAssignmentStrategyConfig = singleton(userTaskAssignmentStrategyConfig, DefaultUserTaskAssignmentStrategyConfig::new);
@@ -73,12 +81,16 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
 
     }
 
-    private <T> T singleton(Iterable<T> value, Supplier<T> defaultValue) {
-        Iterator<T> iterator = value.iterator();
+    private <T> T singleton(Iterable<T> values, Supplier<T> defaultValue) {
+        Iterator<T> iterator = values.iterator();
+        T value = null;
         if (iterator.hasNext()) {
-            return iterator.next();
+            value = iterator.next();
+        } else {
+            value = defaultValue.get();
         }
-        return defaultValue.get();
+        LOG.debug("UserTask config element {}", value);
+        return value;
     }
 
     public DefaultUserTaskConfig(
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
index 757bf30ede..15e2477fc1 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
@@ -18,24 +18,53 @@
  */
 package org.kie.kogito.usertask.impl;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
 
 import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.auth.IdentityProviders;
 import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport;
 import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport.AssignmentType;
+import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.jobs.descriptors.UserTaskInstanceJobDescription;
 import org.kie.kogito.usertask.UserTask;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.UserTaskInstances;
+import org.kie.kogito.usertask.impl.model.DeadlineHelper;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
 import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken;
 import org.kie.kogito.usertask.model.Attachment;
 import org.kie.kogito.usertask.model.Comment;
+import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
+import org.kie.kogito.usertask.model.Reassignment;
+import org.kie.kogito.usertask.model.ScheduleInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
+import static java.util.Collections.emptyMap;
+import static org.kie.kogito.jobs.descriptors.UserTaskInstanceJobDescription.newUserTaskInstanceJobDescriptionBuilder;
+import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.WORKFLOW_ENGINE_USER;
+
 public class DefaultUserTaskInstance implements UserTaskInstance {
 
+    private static Logger LOG = LoggerFactory.getLogger(DefaultUserTaskInstance.class);
+
     private String id;
 
     private String userTaskId;
@@ -67,6 +96,24 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     private KogitoUserTaskEventSupport userTaskEventSupport;
     @JsonIgnore
     private UserTaskLifeCycle userTaskLifeCycle;
+    @JsonIgnore
+    private JobsService jobsService;
+
+    private Collection<DeadlineInfo<Notification>> notStartedDeadlines;
+
+    private Collection<DeadlineInfo<Notification>> notCompletedDeadlines;
+
+    private Collection<DeadlineInfo<Reassignment>> notStartedReassignments;
+
+    private Collection<DeadlineInfo<Reassignment>> notCompletedReassigments;
+
+    private Map<String, Notification> notStartedDeadlinesTimers;
+
+    private Map<String, Notification> notCompletedDeadlinesTimers;
+
+    private Map<String, Reassignment> notStartedReassignmentsTimers;
+
+    private Map<String, Reassignment> notCompletedReassignmentsTimers;
 
     public DefaultUserTaskInstance() {
         this.inputs = new HashMap<>();
@@ -80,6 +127,10 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         this.adminUsers = new HashSet<>();
         this.adminGroups = new HashSet<>();
         this.excludedUsers = new HashSet<>();
+        this.notStartedDeadlinesTimers = new HashMap<>();
+        this.notCompletedDeadlinesTimers = new HashMap<>();
+        this.notStartedReassignmentsTimers = new HashMap<>();
+        this.notCompletedReassignmentsTimers = new HashMap<>();
     }
 
     public DefaultUserTaskInstance(UserTask userTask) {
@@ -88,6 +139,38 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         this.userTask = userTask;
     }
 
+    public Map<String, Notification> getNotCompletedDeadlinesTimers() {
+        return notCompletedDeadlinesTimers;
+    }
+
+    public void setNotCompletedDeadlinesTimers(Map<String, Notification> notCompletedDeadlinesTimers) {
+        this.notCompletedDeadlinesTimers = notCompletedDeadlinesTimers;
+    }
+
+    public Map<String, Reassignment> getNotCompletedReassignmentsTimers() {
+        return notCompletedReassignmentsTimers;
+    }
+
+    public void setNotCompletedReassignmentsTimers(Map<String, Reassignment> notCompletedReassignmentsTimers) {
+        this.notCompletedReassignmentsTimers = notCompletedReassignmentsTimers;
+    }
+
+    public Map<String, Notification> getNotStartedDeadlinesTimers() {
+        return notStartedDeadlinesTimers;
+    }
+
+    public void setNotStartedDeadlinesTimers(Map<String, Notification> notStartedDeadlinesTimers) {
+        this.notStartedDeadlinesTimers = notStartedDeadlinesTimers;
+    }
+
+    public Map<String, Reassignment> getNotStartedReassignmentsTimers() {
+        return notStartedReassignmentsTimers;
+    }
+
+    public void setNotStartedReassignmentsTimers(Map<String, Reassignment> notStartedReassignmentsTimers) {
+        this.notStartedReassignmentsTimers = notStartedReassignmentsTimers;
+    }
+
     public void setUserTaskEventSupport(KogitoUserTaskEventSupport userTaskEventSupport) {
         this.userTaskEventSupport = userTaskEventSupport;
     }
@@ -96,6 +179,10 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         this.userTaskLifeCycle = userTaskLifeCycle;
     }
 
+    public UserTaskLifeCycle getUserTaskLifeCycle() {
+        return userTaskLifeCycle;
+    }
+
     public void setInstances(UserTaskInstances instances) {
         this.instances = instances;
     }
@@ -155,6 +242,11 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         this.externalReferenceId = externalReferenceId;
     }
 
+    @Override
+    public void initialize(Map<String, Object> data, IdentityProvider identity) {
+        transition(this.userTaskLifeCycle.startTransition(), data, identity);
+    }
+
     @Override
     public void transition(String transitionId, Map<String, Object> data, IdentityProvider identity) {
         Optional<UserTaskTransitionToken> next = Optional.of(this.userTaskLifeCycle.newTransitionToken(transitionId, this, data));
@@ -522,6 +614,171 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         return this.comments.stream().filter(e -> e.getId().equals(commentId)).findAny().orElse(null);
     }
 
+    @Override
+    public Collection<DeadlineInfo<Notification>> getNotStartedDeadlines() {
+        return notStartedDeadlines;
+    }
+
+    public void setNotStartedDeadlines(Collection<DeadlineInfo<Notification>> notStartedDeadlines) {
+        this.notStartedDeadlines = notStartedDeadlines;
+    }
+
+    @Override
+    public Collection<DeadlineInfo<Notification>> getNotCompletedDeadlines() {
+        return notCompletedDeadlines;
+    }
+
+    public void setNotCompletedDeadlines(Collection<DeadlineInfo<Notification>> notCompletedDeadlines) {
+        this.notCompletedDeadlines = notCompletedDeadlines;
+    }
+
+    @Override
+    public Collection<DeadlineInfo<Reassignment>> getNotStartedReassignments() {
+        return notStartedReassignments;
+    }
+
+    public void setNotStartedReassignments(Collection<DeadlineInfo<Reassignment>> notStartedReassignments) {
+        this.notStartedReassignments = notStartedReassignments;
+    }
+
+    @Override
+    public Collection<DeadlineInfo<Reassignment>> getNotCompletedReassignments() {
+        return notCompletedReassigments;
+    }
+
+    public void setNotCompletedReassignments(Collection<DeadlineInfo<Reassignment>> notCompletedReassigments) {
+        this.notCompletedReassigments = notCompletedReassigments;
+    }
+
+    public void setJobsService(JobsService jobsService) {
+        this.jobsService = jobsService;
+    }
+
+    private <T> void initTimers(Map<String, T> timers, Collection<DeadlineInfo<T>> deadlines) {
+        for (DeadlineInfo<T> deadline : deadlines) {
+            for (JobDescription jobDescription : toJobDescription(deadline)) {
+                timers.put(jobDescription.id(), deadline.getNotification());
+                this.jobsService.scheduleJob(jobDescription);
+            }
+        }
+        this.updatePersistence();
+    }
+
+    private <T> void stopTimers(Map<String, T> timers) {
+        timers.keySet().stream().filter(Objects::nonNull).forEach(this.jobsService::cancelJob);
+        timers.clear();
+        this.updatePersistence();
+    }
+
+    private List<JobDescription> toJobDescription(DeadlineInfo<?> deadline) {
+        List<JobDescription> jobs = new ArrayList<>();
+        for (ScheduleInfo info : deadline.getScheduleInfo()) {
+            ExpirationTime expirationTime = DeadlineHelper.getExpirationTime(info);
+            jobs.add(newUserTaskInstanceJobDescriptionBuilder()
+                    .generateId()
+                    .expirationTime(expirationTime)
+                    .userTaskInstanceId(this.id)
+                    .build());
+        }
+        return jobs;
+    }
+
+    @Override
+    public void startNotStartedDeadlines() {
+        initTimers(this.notStartedDeadlinesTimers, getNotStartedDeadlines());
+    }
+
+    @Override
+    public void startNotStartedReassignments() {
+        initTimers(this.notStartedReassignmentsTimers, getNotStartedReassignments());
+    }
+
+    @Override
+    public void startNotCompletedDeadlines() {
+        initTimers(this.notCompletedDeadlinesTimers, getNotCompletedDeadlines());
+    }
+
+    @Override
+    public void startNotCompletedReassignments() {
+        initTimers(this.notCompletedReassignmentsTimers, getNotCompletedReassignments());
+    }
+
+    @Override
+    public void stopNotStartedDeadlines() {
+        stopTimers(this.notStartedDeadlinesTimers);
+    }
+
+    @Override
+    public void stopNotStartedReassignments() {
+        stopTimers(this.notStartedReassignmentsTimers);
+    }
+
+    @Override
+    public void stopNotCompletedDeadlines() {
+        stopTimers(this.notCompletedDeadlinesTimers);
+    }
+
+    @Override
+    public void stopNotCompletedReassignments() {
+        stopTimers(this.notCompletedReassignmentsTimers);
+    }
+
+    public void trigger(UserTaskInstanceJobDescription jobDescription) {
+        LOG.trace("trigger timer in user tasks {} and job {}", this, jobDescription);
+        checkAndSendNotitication(jobDescription, notStartedDeadlinesTimers, this::startNotification);
+        checkAndSendNotitication(jobDescription, notCompletedDeadlinesTimers, this::endNotification);
+        checkAndReassign(jobDescription, notStartedReassignmentsTimers);
+        checkAndReassign(jobDescription, notCompletedReassignmentsTimers);
+        this.updatePersistence();
+    }
+
+    private void checkAndSendNotitication(UserTaskInstanceJobDescription timerInstance, Map<String, Notification> timers, Consumer<Notification> publisher) {
+        Notification notification = timers.get(timerInstance.id());
+        if (notification == null) {
+            return;
+        }
+
+        if (timerInstance.expirationTime().repeatLimit() <= 0) {
+            timers.remove(timerInstance.id());
+        }
+        publisher.accept(notification);
+
+    }
+
+    private void startNotification(Notification notification) {
+        if (this.userTaskEventSupport != null) {
+            this.userTaskEventSupport.fireOnUserTaskNotStartedDeadline(this, notification.getData());
+        }
+    }
+
+    private void endNotification(Notification notification) {
+        if (this.userTaskEventSupport != null) {
+            this.userTaskEventSupport.fireOnUserTaskNotCompletedDeadline(this, notification.getData());
+        }
+    }
+
+    private void checkAndReassign(UserTaskInstanceJobDescription timerInstance, Map<String, Reassignment> timers) {
+        Reassignment reassignment = timers.remove(timerInstance.id());
+
+        if (reassignment == null) {
+            return;
+        }
+        reassign(reassignment);
+    }
+
+    private void reassign(Reassignment reassignment) {
+        if (!reassignment.getPotentialUsers().isEmpty()) {
+            setPotentialUsers(reassignment.getPotentialUsers());
+        }
+        if (!reassignment.getPotentialGroups().isEmpty()) {
+            setPotentialGroups(reassignment.getPotentialGroups());
+        }
+
+        this.userTaskLifeCycle.newReassignmentTransitionToken(this, emptyMap()).ifPresent(token -> {
+            this.userTaskLifeCycle.transition(this, token, IdentityProviders.of(WORKFLOW_ENGINE_USER));
+        });
+    }
+
     @Override
     public String toString() {
         return "DefaultUserTaskInstance [id=" + id + ", status=" + status + ", actualOwner=" + actualOwner + ", taskName=" + taskName + ", taskDescription=" + taskDescription + ", taskPriority="
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java
index e291867332..40b616f5c8 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java
@@ -80,6 +80,7 @@ public class DefaultUserTasks implements UserTasks {
         instance.setUserTaskEventSupport(null);
         instance.setUserTaskLifeCycle(null);
         instance.setInstances(null);
+        instance.setJobsService(null);
         return instance;
     }
 
@@ -93,6 +94,7 @@ public class DefaultUserTasks implements UserTasks {
         instance.setUserTaskEventSupport(impl);
         instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
         instance.setInstances(userTaskInstances);
+        instance.setJobsService(userTaskConfig.jobsService());
         return instance;
     }
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
index 256144c484..113228f185 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
@@ -139,6 +139,7 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
     @Override
     public UserTaskInstance create(UserTaskInstance userTaskInstance) {
         try {
+            LOG.trace("create {}", userTaskInstance);
             byte[] data = mapper.writeValueAsBytes(userTaskInstance);
             userTaskInstances.put(userTaskInstance.getId(), data);
             return reconnectUserTaskInstance.apply(userTaskInstance);
@@ -151,6 +152,7 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
     @Override
     public UserTaskInstance update(UserTaskInstance userTaskInstance) {
         try {
+            LOG.trace("update {}", userTaskInstance);
             byte[] data = mapper.writeValueAsBytes(userTaskInstance);
             userTaskInstances.put(userTaskInstance.getId(), data);
             return userTaskInstance;
@@ -166,6 +168,7 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
             if (!userTaskInstances.containsKey(userTaskInstance.getId())) {
                 return null;
             }
+            LOG.trace("remove {}", userTaskInstance);
             userTaskInstances.remove(userTaskInstance.getId());
             return disconnectUserTaskInstance.apply(userTaskInstance);
         } catch (Exception e) {
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java
index 3f1009245e..a2da034e93 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java
@@ -79,15 +79,13 @@ public class UserTaskServiceImpl implements UserTaskService {
 
     @Override
     public Optional<UserTaskView> transition(String taskId, String transitionId, Map<String, Object> data, IdentityProvider identity) {
-        return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> {
-            Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
-            if (userTaskInstance.isEmpty()) {
-                return Optional.empty();
-            }
-            UserTaskInstance ut = userTaskInstance.get();
-            ut.transition(transitionId, data, identity);
-            return Optional.of(toUserTaskView(ut));
-        });
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Optional.empty();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        ut.transition(transitionId, data, identity);
+        return Optional.of(toUserTaskView(ut));
     }
 
     @Override
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java
index 6af8a612fc..5b871d2a8f 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java
@@ -49,6 +49,7 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
     public static final String COMPLETE = "complete";
     public static final String SKIP = "skip";
     public static final String FAIL = "fail";
+    public static final String REASSIGN = "reassign";
 
     public static final UserTaskState INACTIVE = UserTaskState.initalized();
     public static final UserTaskState ACTIVE = UserTaskState.of("Ready");
@@ -66,6 +67,9 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
     private final UserTaskTransition T_RESERVED_SKIPPED = new DefaultUserTransition(SKIP, RESERVED, OBSOLETE, this::skip);
     private final UserTaskTransition T_RESERVED_ERROR = new DefaultUserTransition(FAIL, RESERVED, ERROR, this::fail);
 
+    private final UserTaskTransition T_RESERVED_ACTIVE_R = new DefaultUserTransition(REASSIGN, RESERVED, ACTIVE, this::reassign);
+    private final UserTaskTransition T_ACTIVE_ACTIVE_R = new DefaultUserTransition(REASSIGN, ACTIVE, ACTIVE, this::reassign);
+
     private List<UserTaskTransition> transitions;
 
     public DefaultUserTaskLifeCycle() {
@@ -77,7 +81,9 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
                 T_RESERVED_ACTIVE,
                 T_RESERVED_COMPLETED,
                 T_RESERVED_SKIPPED,
-                T_RESERVED_ERROR);
+                T_RESERVED_ERROR,
+                T_RESERVED_ACTIVE_R,
+                T_ACTIVE_ACTIVE_R);
     }
 
     @Override
@@ -95,6 +101,15 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
         return transition.executor().execute(userTaskInstance, userTaskTransitionToken, identityProvider);
     }
 
+    @Override
+    public Optional<UserTaskTransitionToken> newReassignmentTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data) {
+        try {
+            return Optional.of(newTransitionToken(REASSIGN, userTaskInstance.getStatus(), data));
+        } catch (UserTaskTransitionException e) {
+            return Optional.empty();
+        }
+    }
+
     @Override
     public UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data) {
         return newTransitionToken(COMPLETE, userTaskInstance.getStatus(), data);
@@ -116,11 +131,35 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
         return new DefaultUserTaskTransitionToken(transition.id(), transition.source(), transition.target(), data);
     }
 
+    public Optional<UserTaskTransitionToken> reassign(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        userTaskInstance.stopNotStartedDeadlines();
+        userTaskInstance.stopNotStartedReassignments();
+        userTaskInstance.stopNotCompletedDeadlines();
+        userTaskInstance.stopNotCompletedReassignments();
+
+        // restart the timers
+        userTaskInstance.startNotCompletedDeadlines();
+        userTaskInstance.startNotCompletedReassignments();
+
+        String user = assignStrategy(userTaskInstance, identityProvider);
+        if (user != null) {
+            return Optional.of(newTransitionToken(CLAIM, ACTIVE, Map.of(PARAMETER_USER, user)));
+        }
+        userTaskInstance.startNotStartedDeadlines();
+        userTaskInstance.startNotStartedReassignments();
+        return Optional.empty();
+    }
+
     public Optional<UserTaskTransitionToken> activate(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        userTaskInstance.startNotCompletedDeadlines();
+        userTaskInstance.startNotCompletedReassignments();
+
         String user = assignStrategy(userTaskInstance, identityProvider);
         if (user != null) {
             return Optional.of(newTransitionToken(CLAIM, ACTIVE, Map.of(PARAMETER_USER, user)));
         }
+        userTaskInstance.startNotStartedDeadlines();
+        userTaskInstance.startNotStartedReassignments();
         return Optional.empty();
     }
 
@@ -132,6 +171,8 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
                 defaultUserTaskInstance.setActualOwner(identityProvider.getName());
             }
         }
+        userTaskInstance.stopNotStartedDeadlines();
+        userTaskInstance.stopNotStartedReassignments();
         return Optional.empty();
     }
 
@@ -144,6 +185,10 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
 
     public Optional<UserTaskTransitionToken> complete(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
         token.data().forEach(userTaskInstance::setOutput);
+        userTaskInstance.stopNotStartedDeadlines();
+        userTaskInstance.stopNotStartedReassignments();
+        userTaskInstance.stopNotCompletedDeadlines();
+        userTaskInstance.stopNotCompletedReassignments();
         return Optional.empty();
     }
 
@@ -151,6 +196,10 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
         if (token.data().containsKey(PARAMETER_NOTIFY)) {
             userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY));
         }
+        userTaskInstance.stopNotStartedDeadlines();
+        userTaskInstance.stopNotStartedReassignments();
+        userTaskInstance.stopNotCompletedDeadlines();
+        userTaskInstance.stopNotCompletedReassignments();
         return Optional.empty();
     }
 
@@ -158,6 +207,10 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
         if (token.data().containsKey(PARAMETER_NOTIFY)) {
             userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY));
         }
+        userTaskInstance.stopNotStartedDeadlines();
+        userTaskInstance.stopNotStartedReassignments();
+        userTaskInstance.stopNotCompletedDeadlines();
+        userTaskInstance.stopNotCompletedReassignments();
         return Optional.empty();
     }
 
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/model/DeadlineHelper.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/model/DeadlineHelper.java
index cb541a9c7e..3e0793830b 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/model/DeadlineHelper.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/model/DeadlineHelper.java
@@ -38,6 +38,7 @@ import org.kie.kogito.jobs.DurationExpirationTime;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
 import org.kie.kogito.usertask.model.Reassignment;
 import org.kie.kogito.usertask.model.ScheduleInfo;
 import org.slf4j.Logger;
@@ -89,11 +90,17 @@ public class DeadlineHelper {
     }
 
     public static Collection<DeadlineInfo<Reassignment>> parseReassignments(Object text) {
+        if (text instanceof String textString && textString.isBlank()) {
+            return Collections.emptyList();
+        }
         return parseDeadlines(text, DeadlineHelper::parseReassigment, DeadlineHelper::getReassignmentSchedule);
     }
 
-    public static Collection<DeadlineInfo<Map<String, Object>>> parseDeadlines(Object text) {
-        return parseDeadlines(text, DeadlineHelper::asMap, DeadlineHelper::getSchedulesInfo);
+    public static Collection<DeadlineInfo<Notification>> parseDeadlines(Object text) {
+        if (text instanceof String textString && textString.isBlank()) {
+            return Collections.emptyList();
+        }
+        return parseDeadlines(text, DeadlineHelper::asNotification, DeadlineHelper::getSchedulesInfo);
     }
 
     private static <T> Collection<DeadlineInfo<T>> parseDeadlines(Object text,
@@ -116,7 +123,7 @@ public class DeadlineHelper {
             deadline.setScheduleInfo(scheduleFunction.apply(text.substring(indexOf + 3, text.length() - 1).trim()));
             return deadline;
         }
-        throw new IllegalArgumentException("Invalid formar for dead line expression " + text);
+        throw new IllegalArgumentException("Invalid format for dead line expression " + text);
     }
 
     private static Collection<ScheduleInfo> getReassignmentSchedule(String timeStr) {
@@ -198,6 +205,10 @@ public class DeadlineHelper {
         return repetitionStr.length() > 1 ? Integer.parseInt(repetitionStr.substring(1)) : -1;
     }
 
+    private static Notification asNotification(String text) {
+        return new Notification(asMap(text));
+    }
+
     private static Map<String, Object> asMap(String text) {
         Map<String, Object> result = new HashMap<>();
         for (String str : text.split("\\|")) {
diff --git a/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/model/DeadlineHelperTest.java b/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/model/DeadlineHelperTest.java
index 0bf241c43b..2c022d7672 100644
--- a/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/model/DeadlineHelperTest.java
+++ b/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/model/DeadlineHelperTest.java
@@ -23,11 +23,11 @@ import java.time.Period;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Collection;
-import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.jobs.ExpirationTime;
 import org.kie.kogito.usertask.model.DeadlineInfo;
+import org.kie.kogito.usertask.model.Notification;
 import org.kie.kogito.usertask.model.Reassignment;
 import org.kie.kogito.usertask.model.ScheduleInfo;
 
@@ -38,12 +38,12 @@ public class DeadlineHelperTest {
 
     @Test
     public void testRepetition() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs|body:NotCompleted repeated notification every 5secs]@[R/PT5S]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
-        assertThat(deadlineInfo.getNotification()).containsEntry("body", "NotCompleted repeated notification every 5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("body", "NotCompleted repeated notification every 5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -61,11 +61,11 @@ public class DeadlineHelperTest {
     @Test
     public void testRepetitionWithEndDate() {
         ZonedDateTime future = ZonedDateTime.now().plus(Duration.ofMinutes(2)).plus(Duration.ofSeconds(2));
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs]@[R/PT5S/" + future.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + "]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -82,11 +82,11 @@ public class DeadlineHelperTest {
     @Test
     public void testRepetitionWithEndDateCornerCase() {
         ZonedDateTime future = ZonedDateTime.now().plus(Duration.ofSeconds(5));
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs]@[R/PT5S/" + future.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + "]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -103,11 +103,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testRepetitionWithEndDateAndLimit() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs]@[R2/PT5S/2021-03-18T18:55:01+01:00]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -124,11 +124,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testRepetitionStartEndDate() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs]@[R1/2021-03-18T18:55:01+01:00/2021-03-18T18:55:06+01:00]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -145,11 +145,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testRepetitionWithStartDate() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:5secs]@[R26/2021-03-18T18:55:01+01:00/PT2M]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "5secs");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "5secs");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -166,11 +166,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testExactDate() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:exact date]@[2021-03-18T18:55:01+01:00]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "exact date");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "exact date");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -187,11 +187,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testLargeRepetition() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:more than 1 year]@[R/P1Y3WT1H]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "more than 1 year");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "more than 1 year");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(1);
         ScheduleInfo scheduleInfo = scheduling.iterator().next();
@@ -209,11 +209,11 @@ public class DeadlineHelperTest {
 
     @Test
     public void testMultipleDuration() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:1 and 4 hour]@[PT1H,PT4H]");
         assertThat(deadlines).hasSize(1);
-        DeadlineInfo<Map<String, Object>> deadlineInfo = deadlines.iterator().next();
-        assertThat(deadlineInfo.getNotification()).containsEntry("subject", "1 and 4 hour");
+        DeadlineInfo<Notification> deadlineInfo = deadlines.iterator().next();
+        assertThat(deadlineInfo.getNotification().getData()).containsEntry("subject", "1 and 4 hour");
         Collection<ScheduleInfo> scheduling = deadlineInfo.getScheduleInfo();
         assertThat(scheduling).hasSize(2);
         assertThat(scheduling.stream().filter(s -> s.getDuration().equals(Duration.ofHours(1)) || s.getDuration()
@@ -222,14 +222,14 @@ public class DeadlineHelperTest {
 
     @Test
     public void testMultipleNotification() {
-        Collection<DeadlineInfo<Map<String, Object>>> deadlines = DeadlineHelper.parseDeadlines(
+        Collection<DeadlineInfo<Notification>> deadlines = DeadlineHelper.parseDeadlines(
                 "[subject:1 hour]@[PT1H]^[subject:4 hour]@[PT4H]");
         assertThat(deadlines).hasSize(2);
 
-        for (DeadlineInfo<Map<String, Object>> deadline : deadlines) {
-            if ("1 hour".equals(deadline.getNotification().get("subject"))) {
+        for (DeadlineInfo<Notification> deadline : deadlines) {
+            if ("1 hour".equals(deadline.getNotification().getData().get("subject"))) {
                 assertThat(deadline.getScheduleInfo().iterator().next().getDuration()).isEqualTo(Duration.ofHours(1));
-            } else if ("4 hour".equals(deadline.getNotification().get("subject"))) {
+            } else if ("4 hour".equals(deadline.getNotification().getData().get("subject"))) {
                 assertThat(deadline.getScheduleInfo().iterator().next().getDuration()).isEqualTo(Duration.ofHours(4));
             } else {
                 fail("Unexpected subject value");
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/ApplicationConfigGenerator.java b/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/ApplicationConfigGenerator.java
index 7e54343178..5cec2c94a1 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/ApplicationConfigGenerator.java
+++ b/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/ApplicationConfigGenerator.java
@@ -34,11 +34,12 @@ import org.kie.kogito.codegen.api.template.TemplatedGenerator;
 
 import com.github.javaparser.ast.CompilationUnit;
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
 import com.github.javaparser.ast.expr.MethodCallExpr;
 import com.github.javaparser.ast.expr.NameExpr;
 import com.github.javaparser.ast.expr.ObjectCreationExpr;
 import com.github.javaparser.ast.expr.StringLiteralExpr;
-import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
 
 import static org.kie.kogito.codegen.core.CodegenUtils.newObject;
 
@@ -102,7 +103,7 @@ public class ApplicationConfigGenerator {
                             templatedGenerator,
                             "Compilation unit doesn't contain a class or interface declaration!"));
 
-            initConfigs(getSuperStatement(cls), configClassNames);
+            initConfigs(getInitStatement(cls), configClassNames);
         }
 
         return new GeneratedFile(ConfigGenerator.APPLICATION_CONFIG_TYPE,
@@ -138,8 +139,8 @@ public class ApplicationConfigGenerator {
         this.addons = addons;
     }
 
-    private ExplicitConstructorInvocationStmt getSuperStatement(ClassOrInterfaceDeclaration cls) {
-        return cls.findFirst(ExplicitConstructorInvocationStmt.class)
+    private BlockStmt getInitStatement(ClassOrInterfaceDeclaration cls) {
+        return cls.findFirst(ConstructorDeclaration.class).map(ConstructorDeclaration::getBody)
                 .orElseThrow(() -> new InvalidTemplateException(
                         templatedGenerator,
                         "Impossible to find super invocation"));
@@ -154,10 +155,13 @@ public class ApplicationConfigGenerator {
      * @param superInvocation
      * @param configClassNames
      */
-    private void initConfigs(ExplicitConstructorInvocationStmt superInvocation, Collection<String> configClassNames) {
-        configClassNames.stream()
-                .map(config -> new ObjectCreationExpr()
-                        .setType(config))
-                .forEach(superInvocation::addArgument);
+    private void initConfigs(BlockStmt initInvocation, Collection<String> configClassNames) {
+        initInvocation.findFirst(MethodCallExpr.class).ifPresent(call -> {
+            configClassNames
+                    .stream()
+                    .map(config -> new ObjectCreationExpr().setType(config))
+                    .forEach(call::addArgument);
+        });
+
     }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java
index 64cea466d1..1d872f1d2f 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigJavaTemplate.java
@@ -21,6 +21,6 @@ package $Package$;
 public class ApplicationConfig extends org.kie.kogito.StaticConfig {
 
     public ApplicationConfig() {
-        super($Addons$ /* additional values provided during codegen */);
+        init($Addons$ /* additional values provided during codegen */);
     }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigQuarkusTemplate.java
index a329586e4b..b09fc59ae9 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigQuarkusTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigQuarkusTemplate.java
@@ -24,8 +24,7 @@ import jakarta.enterprise.inject.Instance;
 public class ApplicationConfig extends org.kie.kogito.StaticConfig {
 
     @jakarta.inject.Inject
-    public ApplicationConfig(
-            Instance<org.kie.kogito.KogitoConfig> configs) {
-        super($Addons$, configs);
+    public ApplicationConfig(Instance<org.kie.kogito.KogitoConfig> configs) {
+        init($Addons$, configs);
     }
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java
index 74e3c06683..3b403429d6 100644
--- a/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-core/src/main/resources/class-templates/config/ApplicationConfigSpringTemplate.java
@@ -24,8 +24,7 @@ import java.util.Collection;
 public class ApplicationConfig extends org.kie.kogito.StaticConfig {
 
     @org.springframework.beans.factory.annotation.Autowired
-    public ApplicationConfig(
-            Collection<org.kie.kogito.KogitoConfig> configs) {
-        super($Addons$, configs);
+    public ApplicationConfig(Collection<org.kie.kogito.KogitoConfig> configs) {
+        init($Addons$, configs);
     }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java
index 150c3d62ba..4a6182d0e1 100644
--- a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java
@@ -20,11 +20,14 @@ package org.kie.kogito.codegen.tests;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.Application;
@@ -48,9 +51,13 @@ import org.kie.kogito.process.Processes;
 import org.kie.kogito.process.VariableViolationException;
 import org.kie.kogito.process.WorkItem;
 import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskEventListener;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException;
 import org.kie.kogito.usertask.UserTasks;
+import org.kie.kogito.usertask.events.UserTaskAssignmentEvent;
+import org.kie.kogito.usertask.events.UserTaskDeadlineEvent;
+import org.kie.kogito.usertask.events.UserTaskDeadlineEvent.DeadlineType;
 import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle;
 
 import static java.util.Collections.emptyList;
@@ -59,6 +66,7 @@ import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonMap;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.CLAIM;
 import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.COMPLETE;
 import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.RELEASE;
@@ -953,4 +961,357 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
         assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
     }
+
+    @Test
+    public void testUserTaskNotStartedDeadlineWithExpressionReplacement() throws Exception {
+        CountDownLatch latch = new CountDownLatch(2);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotStartedDeadline.bpmn2");
+        assertThat(app).isNotNull();
+        List<String> subjects = new ArrayList<>();
+        List<String> bodies = new ArrayList<>();
+        List<UserTaskDeadlineEvent.DeadlineType> types = new ArrayList<>();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskDeadline(UserTaskDeadlineEvent event) {
+                subjects.add((String) event.getNotification().get("subject"));
+                bodies.add((String) event.getNotification().get("body"));
+                types.add(event.getType());
+                latch.countDown();
+            }
+
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        latch.await(5L, TimeUnit.SECONDS);
+        String subject = "Task is ready for ${owners[0].id}";
+        String body = "<html>\n"
+                + "                    <body>\n"
+                + "                    Reason this is my subject<br/>\n"
+                + "                    body of notification this is my body\n"
+                + "                    </body>\n"
+                + "                  </html>";
+        assertThat(subjects).containsExactly(subject, subject);
+        assertThat(bodies).containsExactly(body, body);
+        assertThat(types).containsExactly(DeadlineType.Started, DeadlineType.Started);
+    }
+
+    @Test
+    public void testUserTaskNotStartedDeadlineWithExpressionReplacementStopTimer() throws Exception {
+        CountDownLatch latch = new CountDownLatch(2);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotStartedDeadline.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskDeadline(UserTaskDeadlineEvent event) {
+                latch.countDown();
+            }
+
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+        IdentityProvider mary = IdentityProviders.of("mary");
+        List<UserTaskInstance> userTaskInstances = app.get(UserTasks.class).instances().findByIdentity(mary);
+        userTaskInstances.forEach(e -> e.transition(DefaultUserTaskLifeCycle.CLAIM, Collections.emptyMap(), mary));
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertThat(latch.await(5L, TimeUnit.SECONDS)).isFalse();
+
+    }
+
+    @Test
+    public void testUserTaskNotStartedReassign() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotStartedReasssign.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskAssignment(UserTaskAssignmentEvent event) {
+                List<String> users = Arrays.asList(event.getNewUsersId());
+                if (users.size() == 1 && users.contains("mike")) {
+                    latch.countDown();
+                }
+            }
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertThat(latch.await(5L, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Test
+    public void testUserTaskNotStartedReassignStopTimers() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotStartedReasssign.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskAssignment(UserTaskAssignmentEvent event) {
+                List<String> users = Arrays.asList(event.getNewUsersId());
+                if (users.size() == 1 && users.contains("mike")) {
+                    latch.countDown();
+                }
+            }
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+        IdentityProvider mary = IdentityProviders.of("mary");
+        List<UserTaskInstance> userTaskInstances = app.get(UserTasks.class).instances().findByIdentity(mary);
+        userTaskInstances.forEach(e -> e.transition(DefaultUserTaskLifeCycle.CLAIM, Collections.emptyMap(), mary));
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertThat(latch.await(2L, TimeUnit.SECONDS)).isFalse();
+    }
+
+    @Test
+    public void testUserTaskNotCompletedDeadlineWithReplacement() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotCompletedDeadline.bpmn2");
+        assertThat(app).isNotNull();
+
+        List<String> subjects = new ArrayList<>();
+        List<String> bodies = new ArrayList<>();
+        List<UserTaskDeadlineEvent.DeadlineType> types = new ArrayList<>();
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskDeadline(UserTaskDeadlineEvent event) {
+                subjects.add((String) event.getNotification().get("subject"));
+                bodies.add((String) event.getNotification().get("body"));
+                types.add(event.getType());
+                latch.countDown();
+            }
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("body", "my body!!!");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertTrue(latch.await(5L, TimeUnit.SECONDS));
+        latch.await(5L, TimeUnit.SECONDS);
+        String subject = "Not completedTask is ready for ${owners[0].id}";
+        String body = "my body!!!";
+        assertThat(subjects).containsExactly(subject);
+        assertThat(bodies).containsExactly(body);
+        assertThat(types).containsExactly(DeadlineType.Completed);
+    }
+
+    @Test
+    public void testUserTaskNotCompletedDeadlineWithReplacementStopTimer() throws Exception {
+        CountDownLatch latch = new CountDownLatch(2);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotCompletedDeadline.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskDeadline(UserTaskDeadlineEvent event) {
+                latch.countDown();
+            }
+
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+        IdentityProvider mary = IdentityProviders.of("mary");
+        List<UserTaskInstance> userTaskInstances = app.get(UserTasks.class).instances().findByIdentity(mary);
+        userTaskInstances.forEach(e -> {
+            e.transition(DefaultUserTaskLifeCycle.CLAIM, emptyMap(), mary);
+            e.transition(DefaultUserTaskLifeCycle.COMPLETE, emptyMap(), mary);
+        });
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertThat(latch.await(5L, TimeUnit.SECONDS)).isFalse();
+
+    }
+
+    @Test
+    public void testUserTaskNotCompletedReassign() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotCompletedReassign.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskAssignment(UserTaskAssignmentEvent event) {
+                List<String> users = Arrays.asList(event.getNewUsersId());
+                if (users.size() == 1 && users.contains("mike")) {
+                    latch.countDown();
+                }
+            }
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("body", "my body!!!");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertTrue(latch.await(5L, TimeUnit.SECONDS));
+
+    }
+
+    @Test
+    public void testUserTaskNotCompletedReassignStopTimer() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Application app = generateCodeProcessesOnly("usertask/UserTasksNotCompletedReassign.bpmn2");
+        assertThat(app).isNotNull();
+
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskEventListener() {
+            @Override
+            public void onUserTaskAssignment(UserTaskAssignmentEvent event) {
+                List<String> users = Arrays.asList(event.getNewUsersId());
+                if (users.size() == 1 && users.contains("mike")) {
+                    latch.countDown();
+                }
+            }
+
+        });
+        Process<? extends Model> p = app.get(Processes.class).processById("UserTasksDeadline");
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("subject", "this is my subject");
+        parameters.put("body", "this is my body");
+        m.fromMap(parameters);
+
+        // assign custom business key for process instance
+        String businessKey = "business key";
+        ProcessInstance<?> processInstance = p.createInstance(businessKey, m);
+        processInstance.start();
+
+        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+        // verify that custom business key is assigned properly
+        assertThat(processInstance.businessKey()).isEqualTo(businessKey);
+        IdentityProvider mary = IdentityProviders.of("mary");
+        List<UserTaskInstance> userTaskInstances = app.get(UserTasks.class).instances().findByIdentity(mary);
+        userTaskInstances.forEach(e -> {
+            e.transition(DefaultUserTaskLifeCycle.CLAIM, emptyMap(), mary);
+            e.transition(DefaultUserTaskLifeCycle.COMPLETE, emptyMap(), mary);
+        });
+
+        // start another process instance with assigned duplicated business key of already active instance
+        ProcessInstance<? extends Model> otherProcessInstance = p.createInstance(businessKey, m);
+        assertThat(otherProcessInstance.id()).isNotEqualTo(processInstance.id());
+        assertThat(otherProcessInstance.businessKey()).isEqualTo(processInstance.businessKey()).isEqualTo(businessKey);
+        assertThat(latch.await(2L, TimeUnit.SECONDS)).isFalse();
+
+    }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedDeadline.bpmn2 b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedDeadline.bpmn2
new file mode 100644
index 0000000000..1c7eb2d169
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedDeadline.bpmn2
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample"
+             typeLanguage="http://www.java.com/javaTypes"
+             expressionLanguage="http://www.mvel.org/2.0"
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
+             xmlns:g="http://www.jboss.org/drools/flow/gpd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
+             xmlns:tns="http://www.jboss.org/drools">
+
+  <itemDefinition id="_sItem" structureRef="String"/>
+
+  <process processType="Private" isExecutable="true" id="UserTasksDeadline" name="User Task Deadline" >
+
+    <!-- process variables -->
+    <property id="s" itemSubjectRef="_sItem"/>
+    <property id="subject" itemSubjectRef="_sItem"/>
+    <property id="body" itemSubjectRef="_sItem"/>
+
+    <!-- nodes -->
+    <startEvent id="_1" name="StartProcess" />
+    <userTask id="_2" name="Hello" >
+      <ioSpecification>
+        <dataInput id="_2_NotCompletedNotifyInput" name="NotCompletedNotify" />
+        <dataInput id="_2_NotStartedNotifyInput" name="NotStartedNotify" />
+        <dataInput id="_2_CommentInput" name="Comment" />
+        <dataInput id="_2_NotCompletedReassignInput" name="NotCompletedReassign" />
+        <dataInput id="_2_SkippableInput" name="Skippable" />
+        <dataInput id="_2_NotStartedReassignInput" name="NotStartedReassign" />
+        <dataInput id="_2_TaskNameInput" name="TaskName" />
+        <dataInput id="_2_GroupIdInput" name="GroupId" />
+        <dataInput id="_2_PriorityInput" name="Priority" />
+        <inputSet>
+          <dataInputRefs>_2_NotCompletedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_CommentInput</dataInputRefs>
+          <dataInputRefs>_2_NotCompletedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_SkippableInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_TaskNameInput</dataInputRefs>
+          <dataInputRefs>_2_GroupIdInput</dataInputRefs>
+          <dataInputRefs>_2_PriorityInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <targetRef>_2_NotCompletedNotifyInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">[tousers:mike|subject:Not completedTask is ready for ${owners[0].id}|body:#{body}]@[PT2S]</from>
+          <to xsi:type="tFormalExpression">_2_NotCompletedNotifyInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_SkippableInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">false</from>
+          <to xsi:type="tFormalExpression">_2_SkippableInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_TaskNameInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_TaskNameInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_GroupIdInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_GroupIdInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_PriorityInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_PriorityInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <potentialOwner>
+        <resourceAssignmentExpression>
+          <formalExpression>john,mary</formalExpression>
+        </resourceAssignmentExpression>
+      </potentialOwner>
+    </userTask>
+    <endEvent id="_3" name="EndProcess" >
+        <terminateEventDefinition/>
+    </endEvent>
+
+    <!-- connections -->
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />
+
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="UserTask" >
+      <bpmndi:BPMNShape bpmnElement="_1" >
+        <dc:Bounds x="16" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2" >
+        <dc:Bounds x="96" y="16" width="100" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3" >
+        <dc:Bounds x="228" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
+        <di:waypoint x="40" y="40" />
+        <di:waypoint x="146" y="40" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
+        <di:waypoint x="146" y="40" />
+        <di:waypoint x="252" y="40" />
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedReassign.bpmn2 b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedReassign.bpmn2
new file mode 100644
index 0000000000..c0f0b33eb8
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotCompletedReassign.bpmn2
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample"
+             typeLanguage="http://www.java.com/javaTypes"
+             expressionLanguage="http://www.mvel.org/2.0"
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
+             xmlns:g="http://www.jboss.org/drools/flow/gpd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
+             xmlns:tns="http://www.jboss.org/drools">
+
+  <itemDefinition id="_sItem" structureRef="String"/>
+
+  <process processType="Private" isExecutable="true" id="UserTasksDeadline" name="User Task Deadline" >
+
+    <!-- process variables -->
+    <property id="s" itemSubjectRef="_sItem"/>
+    <property id="subject" itemSubjectRef="_sItem"/>
+    <property id="body" itemSubjectRef="_sItem"/>
+
+    <!-- nodes -->
+    <startEvent id="_1" name="StartProcess" />
+    <userTask id="_2" name="Hello" >
+      <ioSpecification>
+        <dataInput id="_2_NotCompletedNotifyInput" name="NotCompletedNotify" />
+        <dataInput id="_2_NotStartedNotifyInput" name="NotStartedNotify" />
+        <dataInput id="_2_CommentInput" name="Comment" />
+        <dataInput id="_2_NotCompletedReassignInput" name="NotCompletedReassign" />
+        <dataInput id="_2_SkippableInput" name="Skippable" />
+        <dataInput id="_2_NotStartedReassignInput" name="NotStartedReassign" />
+        <dataInput id="_2_TaskNameInput" name="TaskName" />
+        <dataInput id="_2_GroupIdInput" name="GroupId" />
+        <dataInput id="_2_PriorityInput" name="Priority" />
+        <inputSet>
+          <dataInputRefs>_2_NotCompletedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_CommentInput</dataInputRefs>
+          <dataInputRefs>_2_NotCompletedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_SkippableInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_TaskNameInput</dataInputRefs>
+          <dataInputRefs>_2_GroupIdInput</dataInputRefs>
+          <dataInputRefs>_2_PriorityInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <targetRef>_2_NotCompletedReassignInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">[users:mike]@[2s]</from>
+          <to xsi:type="tFormalExpression">_2_NotCompletedReassignInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_SkippableInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">false</from>
+          <to xsi:type="tFormalExpression">_2_SkippableInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_TaskNameInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_TaskNameInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_GroupIdInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_GroupIdInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_PriorityInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_PriorityInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <potentialOwner>
+        <resourceAssignmentExpression>
+          <formalExpression>john,mary</formalExpression>
+        </resourceAssignmentExpression>
+      </potentialOwner>
+    </userTask>
+    <endEvent id="_3" name="EndProcess" >
+        <terminateEventDefinition/>
+    </endEvent>
+
+    <!-- connections -->
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />
+
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="UserTask" >
+      <bpmndi:BPMNShape bpmnElement="_1" >
+        <dc:Bounds x="16" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2" >
+        <dc:Bounds x="96" y="16" width="100" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3" >
+        <dc:Bounds x="228" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
+        <di:waypoint x="40" y="40" />
+        <di:waypoint x="146" y="40" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
+        <di:waypoint x="146" y="40" />
+        <di:waypoint x="252" y="40" />
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedDeadline.bpmn2 b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedDeadline.bpmn2
new file mode 100644
index 0000000000..d05098a7bf
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedDeadline.bpmn2
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample"
+             typeLanguage="http://www.java.com/javaTypes"
+             expressionLanguage="http://www.mvel.org/2.0"
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
+             xmlns:g="http://www.jboss.org/drools/flow/gpd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
+             xmlns:tns="http://www.jboss.org/drools">
+
+  <itemDefinition id="_sItem" structureRef="String"/>
+
+  <process processType="Private" isExecutable="true" id="UserTasksDeadline" name="User Task Deadline" >
+
+    <!-- process variables -->
+    <property id="s" itemSubjectRef="_sItem"/>
+    <property id="subject" itemSubjectRef="_sItem"/>
+    <property id="body" itemSubjectRef="_sItem"/>
+
+    <!-- nodes -->
+    <startEvent id="_1" name="StartProcess" />
+    <userTask id="_2" name="Hello" >
+      <ioSpecification>
+        <dataInput id="_2_NotCompletedNotifyInput" name="NotCompletedNotify" />
+        <dataInput id="_2_NotStartedNotifyInput" name="NotStartedNotify" />
+        <dataInput id="_2_CommentInput" name="Comment" />
+        <dataInput id="_2_NotCompletedReassignInput" name="NotCompletedReassign" />
+        <dataInput id="_2_SkippableInput" name="Skippable" />
+        <dataInput id="_2_NotStartedReassignInput" name="NotStartedReassign" />
+        <dataInput id="_2_TaskNameInput" name="TaskName" />
+        <dataInput id="_2_GroupIdInput" name="GroupId" />
+        <dataInput id="_2_PriorityInput" name="Priority" />
+        <inputSet>
+          <dataInputRefs>_2_NotCompletedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_CommentInput</dataInputRefs>
+          <dataInputRefs>_2_NotCompletedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_SkippableInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_TaskNameInput</dataInputRefs>
+          <dataInputRefs>_2_GroupIdInput</dataInputRefs>
+          <dataInputRefs>_2_PriorityInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <targetRef>_2_NotStartedNotifyInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">[tousers:mike|subject:Task is ready for ${owners[0].id}|body:&lt;html&gt;
+                    &lt;body&gt;
+                    Reason #{subject}&lt;br/&gt;
+                    body of notification #{body}
+                    &lt;/body&gt;
+                  &lt;/html&gt;]@[PT2S,PT4S]</from>
+          <to xsi:type="tFormalExpression">_2_NotStartedNotifyInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_SkippableInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">false</from>
+          <to xsi:type="tFormalExpression">_2_SkippableInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_TaskNameInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_TaskNameInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_GroupIdInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_GroupIdInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_PriorityInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_PriorityInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <potentialOwner>
+        <resourceAssignmentExpression>
+          <formalExpression>john,mary</formalExpression>
+        </resourceAssignmentExpression>
+      </potentialOwner>
+    </userTask>
+    <endEvent id="_3" name="EndProcess" >
+        <terminateEventDefinition/>
+    </endEvent>
+
+    <!-- connections -->
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />
+
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="UserTask" >
+      <bpmndi:BPMNShape bpmnElement="_1" >
+        <dc:Bounds x="16" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2" >
+        <dc:Bounds x="96" y="16" width="100" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3" >
+        <dc:Bounds x="228" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
+        <di:waypoint x="40" y="40" />
+        <di:waypoint x="146" y="40" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
+        <di:waypoint x="146" y="40" />
+        <di:waypoint x="252" y="40" />
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedReasssign.bpmn2 b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedReasssign.bpmn2
new file mode 100644
index 0000000000..c2f69199e3
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/usertask/UserTasksNotStartedReasssign.bpmn2
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample"
+             typeLanguage="http://www.java.com/javaTypes"
+             expressionLanguage="http://www.mvel.org/2.0"
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
+             xmlns:g="http://www.jboss.org/drools/flow/gpd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
+             xmlns:tns="http://www.jboss.org/drools">
+
+  <itemDefinition id="_sItem" structureRef="String"/>
+
+  <process processType="Private" isExecutable="true" id="UserTasksDeadline" name="User Task Deadline" >
+
+    <!-- process variables -->
+    <property id="s" itemSubjectRef="_sItem"/>
+    <property id="subject" itemSubjectRef="_sItem"/>
+    <property id="body" itemSubjectRef="_sItem"/>
+
+    <!-- nodes -->
+    <startEvent id="_1" name="StartProcess" />
+    <userTask id="_2" name="Hello" >
+      <ioSpecification>
+        <dataInput id="_2_NotCompletedNotifyInput" name="NotCompletedNotify" />
+        <dataInput id="_2_NotStartedNotifyInput" name="NotStartedNotify" />
+        <dataInput id="_2_CommentInput" name="Comment" />
+        <dataInput id="_2_NotCompletedReassignInput" name="NotCompletedReassign" />
+        <dataInput id="_2_SkippableInput" name="Skippable" />
+        <dataInput id="_2_NotStartedReassignInput" name="NotStartedReassign" />
+        <dataInput id="_2_TaskNameInput" name="TaskName" />
+        <dataInput id="_2_GroupIdInput" name="GroupId" />
+        <dataInput id="_2_PriorityInput" name="Priority" />
+        <inputSet>
+          <dataInputRefs>_2_NotCompletedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedNotifyInput</dataInputRefs>
+          <dataInputRefs>_2_CommentInput</dataInputRefs>
+          <dataInputRefs>_2_NotCompletedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_SkippableInput</dataInputRefs>
+          <dataInputRefs>_2_NotStartedReassignInput</dataInputRefs>
+          <dataInputRefs>_2_TaskNameInput</dataInputRefs>
+          <dataInputRefs>_2_GroupIdInput</dataInputRefs>
+          <dataInputRefs>_2_PriorityInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <targetRef>_2_NotStartedReassignInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">[users:mike]@[PT1S]</from>
+          <to xsi:type="tFormalExpression">_2_NotStartedReassignInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_SkippableInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression">false</from>
+          <to xsi:type="tFormalExpression">_2_SkippableInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_TaskNameInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_TaskNameInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_GroupIdInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_GroupIdInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <dataInputAssociation>
+        <targetRef>_2_PriorityInput</targetRef>
+        <assignment>
+          <from xsi:type="tFormalExpression"></from>
+          <to xsi:type="tFormalExpression">_2_PriorityInput</to>
+        </assignment>
+      </dataInputAssociation>
+      <potentialOwner>
+        <resourceAssignmentExpression>
+          <formalExpression>john,mary</formalExpression>
+        </resourceAssignmentExpression>
+      </potentialOwner>
+    </userTask>
+    <endEvent id="_3" name="EndProcess" >
+        <terminateEventDefinition/>
+    </endEvent>
+
+    <!-- connections -->
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />
+
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="UserTask" >
+      <bpmndi:BPMNShape bpmnElement="_1" >
+        <dc:Bounds x="16" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2" >
+        <dc:Bounds x="96" y="16" width="100" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3" >
+        <dc:Bounds x="228" y="16" width="48" height="48" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2" >
+        <di:waypoint x="40" y="40" />
+        <di:waypoint x="146" y="40" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3" >
+        <di:waypoint x="146" y="40" />
+        <di:waypoint x="252" y="40" />
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java
index 81965e0c4e..9f4e1abb8d 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java
@@ -55,6 +55,9 @@ import org.kie.kogito.internal.SupportedExtensions;
 import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess;
 import org.kie.kogito.process.validation.ValidationException;
 import org.kie.kogito.process.validation.ValidationLogDecorator;
+import org.kie.kogito.usertask.impl.model.DeadlineHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
 
 import com.github.javaparser.StaticJavaParser;
@@ -78,6 +81,8 @@ import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.F
 
 public class UserTaskCodegen extends AbstractGenerator {
 
+    private static Logger LOG = LoggerFactory.getLogger(UserTaskCodegen.class);
+
     private static final String NODE_NAME = "NodeName";
     private static final String DESCRIPTION = "Description";
     private static final String PRIORITY = "Priority";
@@ -219,16 +224,28 @@ public class UserTaskCodegen extends AbstractGenerator {
             block.addStatement(new MethodCallExpr(new ThisExpr(), "setReferenceName", NodeList.nodeList(toStringExpression(info.getParameter(NODE_NAME)))));
             block.addStatement(new MethodCallExpr(new ThisExpr(), "setSkippable", NodeList.nodeList(toStringExpression(info.getParameter("Skippable")))));
 
-            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotStartedDeadLines", NodeList.nodeList(toStringExpression(info.getParameter("NotStartedNotify")))));
-            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotCompletedDeadlines", NodeList.nodeList(toStringExpression(info.getParameter("NotCompletedNotify")))));
-            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotStartedReassignments", NodeList.nodeList(toStringExpression(info.getParameter("NotCompletedReassign")))));
-            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotCompletedReassigments", NodeList.nodeList(toStringExpression(info.getParameter("NotStartedReassign")))));
+            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotStartedDeadLines", NodeList.nodeList(toDeadlineExpression(info.getParameter("NotStartedNotify")))));
+            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotCompletedDeadlines", NodeList.nodeList(toDeadlineExpression(info.getParameter("NotCompletedNotify")))));
+            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotStartedReassignments", NodeList.nodeList(toDeadlineExpression(info.getParameter("NotStartedReassign")))));
+            block.addStatement(new MethodCallExpr(new ThisExpr(), "setNotCompletedReassigments", NodeList.nodeList(toDeadlineExpression(info.getParameter("NotCompletedReassign")))));
 
             generatedFiles.add(new GeneratedFile(GeneratedFileType.SOURCE, UserTaskCodegenHelper.path(info).resolve(className + ".java"), unit.toString()));
         }
         return generatedFiles;
     }
 
+    private Expression toDeadlineExpression(Object parameter) {
+        if (parameter instanceof String stringParam) {
+            try {
+                DeadlineHelper.parseDeadlines(stringParam);
+                return toStringExpression(stringParam);
+            } catch (Exception e) {
+                LOG.debug("to deadline calculation failure. {} it is not a proper expression");
+            }
+        }
+        return new CastExpr(StaticJavaParser.parseType(String.class.getName()), new NullLiteralExpr());
+    }
+
     private Expression toStringExpression(Object value) {
         if (value == null) {
             return new CastExpr(StaticJavaParser.parseType(String.class.getName()), new NullLiteralExpr());
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskContainerGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskContainerGenerator.java
index 364ecf0383..1b05ea184f 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskContainerGenerator.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskContainerGenerator.java
@@ -62,17 +62,15 @@ public class UserTaskContainerGenerator extends AbstractApplicationSection {
         ClassOrInterfaceDeclaration clazzUnit = unit.findFirst(ClassOrInterfaceDeclaration.class).get();
 
         ConstructorDeclaration constructor = clazzUnit.findFirst(ConstructorDeclaration.class).get();
-
-        BlockStmt block = new BlockStmt();
+        BlockStmt block = constructor.getBody();
         NodeList<Expression> arguments = new NodeList<>();
         arguments.add(new NameExpr("application"));
         for (Work descriptor : descriptors) {
             String fqn = UserTaskCodegenHelper.fqnClassName(descriptor);
             arguments.add(new ObjectCreationExpr().setType(StaticJavaParser.parseClassOrInterfaceType(fqn)).setArguments(nodeList(new NameExpr("application"))));
         }
-        block.addStatement(new ExplicitConstructorInvocationStmt().setThis(false).setArguments(arguments));
+        block.findFirst(ExplicitConstructorInvocationStmt.class).ifPresent(e -> e.setArguments(arguments));
         constructor.setBody(block);
-
         return unit;
     }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/ProcessContainerJavaTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/ProcessContainerJavaTemplate.java
index 341baa98a8..40c9fa3486 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/ProcessContainerJavaTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/ProcessContainerJavaTemplate.java
@@ -18,6 +18,14 @@
  */
 package $Package$;
 
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.process.ProcessConfig;
+import org.kie.kogito.process.impl.StaticProcessConfig;
+import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.uow.UnitOfWorkManager;
+
 public class Processes implements org.kie.kogito.process.Processes {
 
     private final Application application;
@@ -25,6 +33,13 @@ public class Processes implements org.kie.kogito.process.Processes {
 
     public Processes(Application application) {
         this.application = application;
+        JobsService jobsService = this.application.config().get(ProcessConfig.class).jobsService();
+        UnitOfWorkManager unitOfWorkManager = this.application.config().get(ProcessConfig.class).unitOfWorkManager();
+        if (jobsService instanceof InMemoryJobService) {
+            InMemoryJobService inMemoryJobService = (InMemoryJobService) jobsService;
+            InMemoryJobContext context = new InMemoryJobContext(null, unitOfWorkManager, this, null);
+            inMemoryJobService.registerJobExecutorFactory(new InMemoryProcessJobExecutorFactory(context));
+        }
     }
 
     public org.kie.kogito.process.Process<? extends org.kie.kogito.Model> processById(String processId) {
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java
index 3b5293ac0e..846b9a88b2 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerJavaTemplate.java
@@ -18,12 +18,25 @@
  */
 package org.kie.kogito.usertask.impl;
 
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryUserTaskJobExecutorFactory;
+import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.usertask.impl.DefaultUserTasks;
 
 public class UserTasks extends DefaultUserTasks {
 
     public UserTasks(Application application) {
         super(application);
+        JobsService jobsService = application.config().get(UserTaskConfig.class).jobsService();
+        UnitOfWorkManager unitOfWorkManager = application.config().get(UserTaskConfig.class).unitOfWorkManager();
+        if (jobsService instanceof InMemoryJobService) {
+            InMemoryJobService inMemoryJobService = (InMemoryJobService) jobsService;
+            InMemoryJobContext context = new InMemoryJobContext(null, unitOfWorkManager, null, this);
+            inMemoryJobService.registerJobExecutorFactory(new InMemoryUserTaskJobExecutorFactory(context));
+        }
     }
 
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java
index 8e98179784..7b341ab3bb 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java
@@ -23,6 +23,11 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.List;
 
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryUserTaskJobExecutorFactory;
+import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl;
@@ -57,6 +62,13 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
             mappedUserTask.put(userTask.id(), userTask);
             LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName());
         }
+        JobsService jobsService = application.config().get(UserTaskConfig.class).jobsService();
+        UnitOfWorkManager unitOfWorkManager = application.config().get(UserTaskConfig.class).unitOfWorkManager();
+        if (jobsService instanceof InMemoryJobService) {
+            InMemoryJobService inMemoryJobService = (InMemoryJobService) jobsService;
+            InMemoryJobContext context = new InMemoryJobContext(null, unitOfWorkManager, null, this);
+            inMemoryJobService.registerJobExecutorFactory(new InMemoryUserTaskJobExecutorFactory(context));
+        }
     }
 
     public UserTask userTaskById(String userTaskId) {
@@ -78,6 +90,7 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
         instance.setUserTaskEventSupport(null);
         instance.setUserTaskLifeCycle(null);
         instance.setInstances(null);
+        instance.setJobsService(null);
         return instance;
     }
 
@@ -91,6 +104,7 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
         instance.setUserTaskEventSupport(impl);
         instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
         instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances());
+        instance.setJobsService(userTaskConfig.jobsService());
         return instance;
     }
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java
index 9785b44d7c..0d17584686 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java
@@ -24,6 +24,11 @@ import java.util.Map;
 import java.util.List;
 
 import org.kie.kogito.Application;
+import org.kie.kogito.jobs.JobsService;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryUserTaskJobExecutorFactory;
+import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
 import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl;
@@ -58,6 +63,13 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
             mappedUserTask.put(userTask.id(), userTask);
             LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName());
         }
+        JobsService jobsService = application.config().get(UserTaskConfig.class).jobsService();
+        UnitOfWorkManager unitOfWorkManager = application.config().get(UserTaskConfig.class).unitOfWorkManager();
+        if (jobsService instanceof InMemoryJobService) {
+            InMemoryJobService inMemoryJobService = (InMemoryJobService) jobsService;
+            InMemoryJobContext context = new InMemoryJobContext(null, unitOfWorkManager, null, this);
+            inMemoryJobService.registerJobExecutorFactory(new InMemoryUserTaskJobExecutorFactory(context));
+        }
     }
 
     public UserTask userTaskById(String userTaskId) {
@@ -79,6 +91,7 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
         instance.setUserTaskEventSupport(null);
         instance.setUserTaskLifeCycle(null);
         instance.setInstances(null);
+        instance.setJobsService(null);
         return instance;
     }
 
@@ -92,6 +105,7 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
         instance.setUserTaskEventSupport(impl);
         instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
         instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances());
+        instance.setJobsService(userTaskConfig.jobsService());
         return instance;
     }
 }
\ No newline at end of file
diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java
index 5cf827f291..dfc9d750d3 100644
--- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java
@@ -28,7 +28,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import org.junit.jupiter.api.Test;
 import org.kie.api.event.process.ProcessCompletedEvent;
 import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener;
+import org.kie.kogito.jobs.JobsService;
 import org.kie.kogito.process.Process;
+import org.kie.kogito.process.ProcessConfig;
+import org.kie.kogito.process.Processes;
 import org.kie.kogito.process.validation.ValidationException;
 import org.kie.kogito.serverless.workflow.actions.SysoutAction;
 import org.kie.kogito.serverless.workflow.actions.WorkflowLogLevel;
@@ -38,6 +41,10 @@ import org.kie.kogito.serverless.workflow.models.JsonNodeModel;
 import org.kie.kogito.serverless.workflow.parser.types.SysOutTypeHandler;
 import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils;
 import org.kie.kogito.serverless.workflow.utils.KogitoProcessContextResolver;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
+import org.kie.kogito.uow.UnitOfWorkManager;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -141,6 +148,13 @@ public class StaticFluentWorkflowApplicationTest {
         OperationStateBuilder sleepState = operation().action(call(expr("inc", ".count=.count+1")).sleepAfter(Duration.ofSeconds(1)));
 
         try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) {
+            ProcessConfig processConfig = application.config().get(ProcessConfig.class);
+            JobsService jobService = processConfig.jobsService();
+            Processes processes = application.get(Processes.class);
+            UnitOfWorkManager unitOfWorkManager = processConfig.unitOfWorkManager();
+            if (jobService instanceof InMemoryJobService inMemoryJobService) {
+                inMemoryJobService.registerJobExecutorFactory(new InMemoryProcessJobExecutorFactory(new InMemoryJobContext(null, unitOfWorkManager, processes, null)));
+            }
             Workflow workflow = workflow("Polling").start(startTask)
                     .next(sleepState)
                     .next(pollTask)
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineEntityMapper.java
similarity index 61%
copy from quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineEntityMapper.java
index e3f6c79e1a..3067cbb77f 100644
--- a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineEntityMapper.java
@@ -19,21 +19,21 @@
 
 package org.jbpm.usertask.jpa.quarkus.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskDeadlineEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineRepository;
 
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 @ApplicationScoped
-public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class QuarkusTaskDeadlineEntityMapper extends TaskDeadlineEntityMapper {
 
-    QuarkusUserTaskInstanceEntityMapper() {
-        this(null, null, null, null, null);
+    public QuarkusTaskDeadlineEntityMapper() {
+        super(null);
     }
 
     @Inject
-    public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public QuarkusTaskDeadlineEntityMapper(TaskDeadlineRepository repository) {
+        super(repository);
     }
 }
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineTimerEntityMapper.java
similarity index 61%
copy from quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineTimerEntityMapper.java
index e3f6c79e1a..d7377affa7 100644
--- a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskDeadlineTimerEntityMapper.java
@@ -19,21 +19,21 @@
 
 package org.jbpm.usertask.jpa.quarkus.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskDeadlineTimerEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineTimerRepository;
 
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 @ApplicationScoped
-public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class QuarkusTaskDeadlineTimerEntityMapper extends TaskDeadlineTimerEntityMapper {
 
-    QuarkusUserTaskInstanceEntityMapper() {
-        this(null, null, null, null, null);
+    public QuarkusTaskDeadlineTimerEntityMapper() {
+        super(null);
     }
 
     @Inject
-    public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public QuarkusTaskDeadlineTimerEntityMapper(TaskDeadlineTimerRepository repository) {
+        super(repository);
     }
 }
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentEntityMapper.java
similarity index 61%
copy from quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentEntityMapper.java
index e3f6c79e1a..1d86a96522 100644
--- a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentEntityMapper.java
@@ -19,21 +19,21 @@
 
 package org.jbpm.usertask.jpa.quarkus.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskReassignmentEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentRepository;
 
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 @ApplicationScoped
-public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class QuarkusTaskReassignmentEntityMapper extends TaskReassignmentEntityMapper {
 
-    QuarkusUserTaskInstanceEntityMapper() {
-        this(null, null, null, null, null);
+    public QuarkusTaskReassignmentEntityMapper() {
+        super(null);
     }
 
     @Inject
-    public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public QuarkusTaskReassignmentEntityMapper(TaskReassignmentRepository repository) {
+        super(repository);
     }
 }
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentTimerEntityMapper.java
similarity index 61%
copy from quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentTimerEntityMapper.java
index e3f6c79e1a..f5353c6ccb 100644
--- a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskReassignmentTimerEntityMapper.java
@@ -19,21 +19,21 @@
 
 package org.jbpm.usertask.jpa.quarkus.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskReassignmentTimerEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentTimerRepository;
 
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 @ApplicationScoped
-public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class QuarkusTaskReassignmentTimerEntityMapper extends TaskReassignmentTimerEntityMapper {
 
-    QuarkusUserTaskInstanceEntityMapper() {
-        this(null, null, null, null, null);
+    public QuarkusTaskReassignmentTimerEntityMapper() {
+        super(null);
     }
 
     @Inject
-    public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public QuarkusTaskReassignmentTimerEntityMapper(TaskReassignmentTimerRepository repository) {
+        super(repository);
     }
 }
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
index e3f6c79e1a..372301f761 100644
--- a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
@@ -19,21 +19,24 @@
 
 package org.jbpm.usertask.jpa.quarkus.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import java.util.Collections;
+
+import org.jbpm.usertask.jpa.mapper.EntityMapper;
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
 
 import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Instance;
 import jakarta.inject.Inject;
 
 @ApplicationScoped
 public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
 
     QuarkusUserTaskInstanceEntityMapper() {
-        this(null, null, null, null, null);
+        super(Collections.emptyList());
     }
 
     @Inject
-    public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public QuarkusUserTaskInstanceEntityMapper(Instance<EntityMapper> mappers) {
+        super(mappers);
     }
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineRepository.java
similarity index 63%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineRepository.java
index 6e85081ff7..9cf4d5eb09 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineRepository.java
@@ -17,19 +17,24 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.repository;
+package org.jbpm.usertask.jpa.quarkus.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 
-    public TaskInputRepository(UserTaskJPAContext context) {
-        super(context);
+@ApplicationScoped
+public class QuarkusTaskDeadlineRepository extends TaskDeadlineRepository {
+
+    QuarkusTaskDeadlineRepository() {
+        super(null);
     }
 
-    @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    @Inject
+    public QuarkusTaskDeadlineRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineTimerRepository.java
similarity index 62%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineTimerRepository.java
index 6e85081ff7..782b20a293 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskDeadlineTimerRepository.java
@@ -17,19 +17,24 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.repository;
+package org.jbpm.usertask.jpa.quarkus.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineTimerRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 
-    public TaskInputRepository(UserTaskJPAContext context) {
-        super(context);
+@ApplicationScoped
+public class QuarkusTaskDeadlineTimerRepository extends TaskDeadlineTimerRepository {
+
+    QuarkusTaskDeadlineTimerRepository() {
+        this(null);
     }
 
-    @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    @Inject
+    public QuarkusTaskDeadlineTimerRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentRepository.java
similarity index 62%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentRepository.java
index 6e85081ff7..e597dddc6c 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentRepository.java
@@ -17,19 +17,24 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.repository;
+package org.jbpm.usertask.jpa.quarkus.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 
-    public TaskInputRepository(UserTaskJPAContext context) {
-        super(context);
+@ApplicationScoped
+public class QuarkusTaskReassignmentRepository extends TaskReassignmentRepository {
+
+    QuarkusTaskReassignmentRepository() {
+        super(null);
     }
 
-    @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    @Inject
+    public QuarkusTaskReassignmentRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentTimerRepository.java
similarity index 61%
copy from addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
copy to quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentTimerRepository.java
index 6e85081ff7..a92d3b6547 100644
--- a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskReassignmentTimerRepository.java
@@ -17,19 +17,24 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.repository;
+package org.jbpm.usertask.jpa.quarkus.repository;
 
-import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
-import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentTimerRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 
-public class TaskInputRepository extends BaseRepository<TaskInputEntity, TaskDataEntityPK> {
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 
-    public TaskInputRepository(UserTaskJPAContext context) {
-        super(context);
+@ApplicationScoped
+public class QuarkusTaskReassignmentTimerRepository extends TaskReassignmentTimerRepository {
+
+    QuarkusTaskReassignmentTimerRepository() {
+        super(null);
     }
 
-    @Override
-    public Class<TaskInputEntity> getEntityClass() {
-        return TaskInputEntity.class;
+    @Inject
+    public QuarkusTaskReassignmentTimerRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/quarkus/addons/jobs/common/messaging/src/main/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsService.java b/quarkus/addons/jobs/common/messaging/src/main/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsService.java
index a238140469..3f61e1fe1d 100644
--- a/quarkus/addons/jobs/common/messaging/src/main/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsService.java
+++ b/quarkus/addons/jobs/common/messaging/src/main/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsService.java
@@ -23,10 +23,9 @@ import java.util.concurrent.CompletableFuture;
 
 import org.eclipse.microprofile.reactive.messaging.Emitter;
 import org.eclipse.microprofile.reactive.messaging.Message;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.JobsService;
 import org.kie.kogito.jobs.JobsServiceException;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
 import org.kie.kogito.jobs.service.api.Job;
 import org.kie.kogito.jobs.service.api.JobLookupId;
 import org.kie.kogito.jobs.service.api.event.CreateJobEvent;
@@ -73,12 +72,7 @@ public abstract class AbstractReactiveMessagingJobsService implements JobsServic
     }
 
     @Override
-    public String scheduleProcessJob(ProcessJobDescription description) {
-        throw new UnsupportedOperationException("Scheduling for process jobs is not yet implemented");
-    }
-
-    @Override
-    public String scheduleProcessInstanceJob(ProcessInstanceJobDescription description) {
+    public String scheduleJob(JobDescription description) {
         Job job = buildCallbackPatternJob(description, buildCallbackURI(description, serviceUrl.toString()), objectMapper);
         LOGGER.debug("scheduleProcessInstanceJob job: {}", job);
         CreateJobEvent event = CreateJobEvent.builder()
diff --git a/quarkus/addons/jobs/common/messaging/src/test/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsServiceTest.java b/quarkus/addons/jobs/common/messaging/src/test/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsServiceTest.java
index 3d22a9bfb1..ccb448a7af 100644
--- a/quarkus/addons/jobs/common/messaging/src/test/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsServiceTest.java
+++ b/quarkus/addons/jobs/common/messaging/src/test/java/org/kie/kogito/jobs/messaging/quarkus/AbstractReactiveMessagingJobsServiceTest.java
@@ -29,10 +29,10 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.kie.kogito.jobs.ExactExpirationTime;
 import org.kie.kogito.jobs.ExpirationTime;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.JobsServiceException;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
 import org.kie.kogito.jobs.api.JobCallbackPayload;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.service.api.Job;
 import org.kie.kogito.jobs.service.api.JobLookupId;
 import org.kie.kogito.jobs.service.api.TemporalUnit;
@@ -105,12 +105,12 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
 
     @Test
     protected void scheduleProcessInstanceJobSuccessful() throws Exception {
-        ProcessInstanceJobDescription description = mockProcessInstanceJobDescription();
+        JobDescription description = mockProcessInstanceJobDescription();
         CreateJobEvent expectedEvent = mockExpectedCreateJobEvent();
         doReturn(SERIALIZED_EVENT).when(objectMapper).writeValueAsString(any(CreateJobEvent.class));
         doReturn(JSON_PAYLOAD).when(objectMapper).valueToTree(any(JobCallbackPayload.class));
 
-        jobsService.scheduleProcessInstanceJob(description);
+        jobsService.scheduleJob(description);
 
         verifyCreateJobEventWasCreated(1, expectedEvent);
         verifyEmitterWasInvoked(1, SERIALIZED_EVENT);
@@ -137,7 +137,7 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
         // Clear the errors and produce a second execution that must work fine.
         eventsEmitter.clearErrors();
         doReturn(SERIALIZED_SECOND_EVENT).when(objectMapper).writeValueAsString(any(CreateJobEvent.class));
-        jobsService.scheduleProcessInstanceJob(description);
+        jobsService.scheduleJob(description);
 
         verifyCreateJobEventWasCreated(2, expectedEvent, expectedEvent);
         verifyEmitterWasInvoked(2, SERIALIZED_EVENT, SERIALIZED_SECOND_EVENT);
@@ -148,7 +148,7 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
         RuntimeException nackError = new RuntimeException(ERROR);
         eventsEmitter.setNackError(nackError);
         // ensure the execution failed as programmed
-        assertThatThrownBy(() -> jobsService.scheduleProcessInstanceJob(description))
+        assertThatThrownBy(() -> jobsService.scheduleJob(description))
                 .isInstanceOf(JobsServiceException.class)
                 .hasMessageContaining("Error while emitting JobCloudEvent")
                 .hasCause(nackError);
@@ -175,7 +175,7 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
         // Clear the errors and produce a second execution that must work fine.
         eventsEmitter.clearErrors();
         doReturn(SERIALIZED_SECOND_EVENT).when(objectMapper).writeValueAsString(any(CreateJobEvent.class));
-        jobsService.scheduleProcessInstanceJob(description);
+        jobsService.scheduleJob(description);
 
         verifyCreateJobEventWasCreated(2, expectedEvent, expectedEvent);
         verifyEmitterWasInvoked(2, SERIALIZED_EVENT, SERIALIZED_SECOND_EVENT);
@@ -186,7 +186,7 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
         RuntimeException fatalError = new RuntimeException(FATAL_ERROR);
         eventsEmitter.setFatalError(fatalError);
         // ensure the execution failed as programmed
-        assertThatThrownBy(() -> jobsService.scheduleProcessInstanceJob(description))
+        assertThatThrownBy(() -> jobsService.scheduleJob(description))
                 .isInstanceOf(JobsServiceException.class)
                 .hasMessageContaining("Error while emitting JobCloudEvent")
                 .hasCause(fatalError);
@@ -275,15 +275,8 @@ public abstract class AbstractReactiveMessagingJobsServiceTest<T extends Abstrac
                 .hasCause(fatalError);
     }
 
-    @Test
-    protected void scheduleProcessJob() {
-        ProcessJobDescription description = ProcessJobDescription.of(EXPIRATION_TIME, PROCESS_ID);
-        assertThatThrownBy(() -> jobsService.scheduleProcessJob(description))
-                .isInstanceOf(UnsupportedOperationException.class);
-    }
-
     protected ProcessInstanceJobDescription mockProcessInstanceJobDescription() {
-        return ProcessInstanceJobDescription.builder()
+        return ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
                 .id(JOB_ID)
                 .timerId(TIMER_ID)
                 .expirationTime(EXPIRATION_TIME)
diff --git a/quarkus/addons/jobs/common/rest-callback/src/main/java/org/kie/kogito/jobs/quarkus/common/CallbackJobsServiceResource.java b/quarkus/addons/jobs/common/rest-callback/src/main/java/org/kie/kogito/jobs/quarkus/common/CallbackJobsServiceResource.java
index 0e67866619..cd4972bc63 100644
--- a/quarkus/addons/jobs/common/rest-callback/src/main/java/org/kie/kogito/jobs/quarkus/common/CallbackJobsServiceResource.java
+++ b/quarkus/addons/jobs/common/rest-callback/src/main/java/org/kie/kogito/jobs/quarkus/common/CallbackJobsServiceResource.java
@@ -22,6 +22,7 @@ import java.util.Optional;
 
 import org.kie.kogito.Application;
 import org.kie.kogito.Model;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.api.JobCallbackPayload;
 import org.kie.kogito.process.Process;
 import org.kie.kogito.process.Processes;
@@ -42,14 +43,13 @@ import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.JOBS_CALLBACK_POST_URI;
-import static org.kie.kogito.jobs.api.JobCallbackResourceDef.JOBS_CALLBACK_URI;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.LIMIT;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.LIMIT_DEFAULT_VALUE;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.PROCESS_ID;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.PROCESS_INSTANCE_ID;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.TIMER_ID;
 
-@Path(JOBS_CALLBACK_URI)
+@Path(JobDescription.JOBS_CALLBACK_URI)
 public class CallbackJobsServiceResource {
 
     @Inject
diff --git a/quarkus/addons/jobs/management/runtime/src/main/java/org/kie/kogito/jobs/management/quarkus/VertxJobsService.java b/quarkus/addons/jobs/management/runtime/src/main/java/org/kie/kogito/jobs/management/quarkus/VertxJobsService.java
index a7989885d0..c642ff6674 100644
--- a/quarkus/addons/jobs/management/runtime/src/main/java/org/kie/kogito/jobs/management/quarkus/VertxJobsService.java
+++ b/quarkus/addons/jobs/management/runtime/src/main/java/org/kie/kogito/jobs/management/quarkus/VertxJobsService.java
@@ -21,8 +21,7 @@ package org.kie.kogito.jobs.management.quarkus;
 import java.net.URI;
 
 import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.management.RestJobsService;
 import org.kie.kogito.jobs.service.api.Job;
 import org.slf4j.Logger;
@@ -89,13 +88,7 @@ public class VertxJobsService extends RestJobsService {
     }
 
     @Override
-    public String scheduleProcessJob(ProcessJobDescription description) {
-
-        throw new UnsupportedOperationException("Scheduling for process jobs is not yet implemented");
-    }
-
-    @Override
-    public String scheduleProcessInstanceJob(ProcessInstanceJobDescription description) {
+    public String scheduleJob(JobDescription description) {
         String callback = getCallbackEndpoint(description);
         LOGGER.debug("Job to be scheduled {} with callback URL {}", description, callback);
         final Job job = buildJob(description, callback);
diff --git a/quarkus/addons/jobs/management/runtime/src/test/java/org/kie/kogito/jobs/management/quarkus/VertxJobsServiceTest.java b/quarkus/addons/jobs/management/runtime/src/test/java/org/kie/kogito/jobs/management/quarkus/VertxJobsServiceTest.java
index 578d7f954c..56bc91ed72 100644
--- a/quarkus/addons/jobs/management/runtime/src/test/java/org/kie/kogito/jobs/management/quarkus/VertxJobsServiceTest.java
+++ b/quarkus/addons/jobs/management/runtime/src/test/java/org/kie/kogito/jobs/management/quarkus/VertxJobsServiceTest.java
@@ -20,8 +20,8 @@ package org.kie.kogito.jobs.management.quarkus;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.api.JobCallbackPayload;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.management.RestJobsServiceTest;
 import org.kie.kogito.jobs.service.api.Job;
 import org.mockito.ArgumentCaptor;
@@ -87,7 +87,7 @@ class VertxJobsServiceTest extends RestJobsServiceTest<VertxJobsService> {
         when(webClient.post(anyString())).thenReturn(request);
         when(objectMapper.valueToTree(any(JobCallbackPayload.class))).thenReturn(JSON_PAYLOAD);
         ProcessInstanceJobDescription processInstanceJobDescription = buildProcessInstanceJobDescription();
-        tested.scheduleProcessInstanceJob(processInstanceJobDescription);
+        tested.scheduleJob(processInstanceJobDescription);
         verify(webClient).post("/v2/jobs");
         ArgumentCaptor<Job> jobArgumentCaptor = forClass(Job.class);
         verify(request).sendJson(jobArgumentCaptor.capture(), any(Handler.class));
diff --git a/quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java b/quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
index 7b282dc8f9..963f5958fc 100644
--- a/quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
+++ b/quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
@@ -27,10 +27,13 @@ import org.kie.kogito.jobs.JobsService;
 import org.kie.kogito.process.ProcessVersionResolver;
 import org.kie.kogito.process.Processes;
 import org.kie.kogito.process.version.ProjectVersionProcessVersionResolver;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
 import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
 import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
 import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
 import org.kie.kogito.uow.UnitOfWorkManager;
+import org.kie.kogito.usertask.UserTasks;
 
 import io.quarkus.arc.DefaultBean;
 import io.quarkus.arc.properties.IfBuildProperty;
@@ -61,8 +64,11 @@ public class KogitoBeanProducer {
 
     @DefaultBean
     @Produces
-    JobsService jobsService(Instance<Processes> processes, UnitOfWorkManager uowm, ScheduledExecutorService executor) {
-        return InMemoryJobService.get(processes.isResolvable() ? processes.get() : null, uowm, executor);
+    JobsService jobsService(Instance<Processes> processes, Instance<UserTasks> userTasks, UnitOfWorkManager uowm, ScheduledExecutorService executor) {
+        InMemoryJobContext context = new InMemoryJobContext(null, uowm, processes.isResolvable() ? processes.get() : null, userTasks.isResolvable() ? userTasks.get() : null);
+        InMemoryJobService inMemoryJobService = new InMemoryJobService(executor);
+        inMemoryJobService.registerJobExecutorFactory(new InMemoryProcessJobExecutorFactory(context));
+        return inMemoryJobService;
     }
 
     @Produces
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineEntityMapper.java
similarity index 64%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineEntityMapper.java
index 6c753b1b6a..03201da8fb 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineEntityMapper.java
@@ -19,16 +19,20 @@
 
 package org.jbpm.usertask.jpa.springboot.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskDeadlineEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskDeadlineEntityMapper extends TaskDeadlineEntityMapper {
+
+    public SpringBootTaskDeadlineEntityMapper() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskDeadlineEntityMapper(TaskDeadlineRepository repository) {
+        super(repository);
     }
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineTimerEntityMapper.java
similarity index 64%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineTimerEntityMapper.java
index 6c753b1b6a..399a958431 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskDeadlineTimerEntityMapper.java
@@ -19,16 +19,20 @@
 
 package org.jbpm.usertask.jpa.springboot.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskDeadlineTimerEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineTimerRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskDeadlineTimerEntityMapper extends TaskDeadlineTimerEntityMapper {
+
+    public SpringBootTaskDeadlineTimerEntityMapper() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskDeadlineTimerEntityMapper(TaskDeadlineTimerRepository repository) {
+        super(repository);
     }
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentEntityMapper.java
similarity index 64%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentEntityMapper.java
index 6c753b1b6a..7e32aaee44 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentEntityMapper.java
@@ -19,16 +19,20 @@
 
 package org.jbpm.usertask.jpa.springboot.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskReassignmentEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskReassignmentEntityMapper extends TaskReassignmentEntityMapper {
+
+    public SpringBootTaskReassignmentEntityMapper() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskReassignmentEntityMapper(TaskReassignmentRepository repository) {
+        super(repository);
     }
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentTimerEntityMapper.java
similarity index 64%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentTimerEntityMapper.java
index 6c753b1b6a..47eb8f1800 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskReassignmentTimerEntityMapper.java
@@ -19,16 +19,20 @@
 
 package org.jbpm.usertask.jpa.springboot.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.TaskReassignmentTimerEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentTimerRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskReassignmentTimerEntityMapper extends TaskReassignmentTimerEntityMapper {
+
+    public SpringBootTaskReassignmentTimerEntityMapper() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskReassignmentTimerEntityMapper(TaskReassignmentTimerRepository repository) {
+        super(repository);
     }
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
index 6c753b1b6a..33462df7f0 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
@@ -19,7 +19,10 @@
 
 package org.jbpm.usertask.jpa.springboot.mapper;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import java.util.List;
+
+import org.jbpm.usertask.jpa.mapper.EntityMapper;
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -27,8 +30,8 @@ import org.springframework.stereotype.Component;
 public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootUserTaskInstanceEntityMapper(List<EntityMapper> mappers) {
+        super(mappers);
     }
+
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineRepository.java
similarity index 61%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineRepository.java
index 6c753b1b6a..fe7f742cd4 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineRepository.java
@@ -17,18 +17,23 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.springboot.mapper;
+package org.jbpm.usertask.jpa.springboot.repository;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskDeadlineRepository extends TaskDeadlineRepository {
+
+    SpringBootTaskDeadlineRepository() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskDeadlineRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineTimerRepository.java
similarity index 61%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineTimerRepository.java
index 6c753b1b6a..85a1356819 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskDeadlineTimerRepository.java
@@ -17,18 +17,23 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.springboot.mapper;
+package org.jbpm.usertask.jpa.springboot.repository;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.repository.TaskDeadlineTimerRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskDeadlineTimerRepository extends TaskDeadlineTimerRepository {
+
+    SpringBootTaskDeadlineTimerRepository() {
+        this(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskDeadlineTimerRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentRepository.java
similarity index 61%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentRepository.java
index 6c753b1b6a..1ccc6987f0 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentRepository.java
@@ -17,18 +17,23 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.springboot.mapper;
+package org.jbpm.usertask.jpa.springboot.repository;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskReassignmentRepository extends TaskReassignmentRepository {
+
+    SpringBootTaskReassignmentRepository() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskReassignmentRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentTimerRepository.java
similarity index 61%
copy from springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
copy to springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentTimerRepository.java
index 6c753b1b6a..076452807f 100644
--- a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskReassignmentTimerRepository.java
@@ -17,18 +17,23 @@
  * under the License.
  */
 
-package org.jbpm.usertask.jpa.springboot.mapper;
+package org.jbpm.usertask.jpa.springboot.repository;
 
-import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.repository.TaskReassignmentTimerRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+public class SpringBootTaskReassignmentTimerRepository extends TaskReassignmentTimerRepository {
+
+    SpringBootTaskReassignmentTimerRepository() {
+        super(null);
+    }
 
     @Autowired
-    public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
-            TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
-        super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+    public SpringBootTaskReassignmentTimerRepository(UserTaskJPAContext context) {
+        super(context);
     }
+
 }
diff --git a/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/CallbackJobsServiceResource.java b/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/CallbackJobsServiceResource.java
index 752c6a84c7..438ac1659f 100644
--- a/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/CallbackJobsServiceResource.java
+++ b/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/CallbackJobsServiceResource.java
@@ -39,8 +39,8 @@ import org.springframework.web.bind.annotation.RestController;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import static org.kie.kogito.jobs.JobDescription.JOBS_CALLBACK_URI;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.JOBS_CALLBACK_POST_URI;
-import static org.kie.kogito.jobs.api.JobCallbackResourceDef.JOBS_CALLBACK_URI;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.LIMIT;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.LIMIT_DEFAULT_VALUE;
 import static org.kie.kogito.jobs.api.JobCallbackResourceDef.PROCESS_ID;
diff --git a/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsService.java b/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsService.java
index ac20763e55..b89392c51e 100644
--- a/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsService.java
+++ b/springboot/addons/jobs/src/main/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsService.java
@@ -18,8 +18,7 @@
  */
 package org.kie.kogito.jobs.management.springboot;
 
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
-import org.kie.kogito.jobs.ProcessJobDescription;
+import org.kie.kogito.jobs.JobDescription;
 import org.kie.kogito.jobs.management.RestJobsService;
 import org.kie.kogito.jobs.service.api.Job;
 import org.slf4j.Logger;
@@ -73,13 +72,7 @@ public class SpringRestJobsService extends RestJobsService {
     }
 
     @Override
-    public String scheduleProcessJob(ProcessJobDescription description) {
-
-        throw new UnsupportedOperationException("Scheduling for process jobs is not yet implemented");
-    }
-
-    @Override
-    public String scheduleProcessInstanceJob(ProcessInstanceJobDescription description) {
+    public String scheduleJob(JobDescription description) {
         String callback = getCallbackEndpoint(description);
         LOGGER.debug("Job to be scheduled {} with callback URL {}", description, callback);
         final Job job = buildJob(description, callback);
diff --git a/springboot/addons/jobs/src/test/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsServiceTest.java b/springboot/addons/jobs/src/test/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsServiceTest.java
index 8804f608a8..0daf928680 100644
--- a/springboot/addons/jobs/src/test/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsServiceTest.java
+++ b/springboot/addons/jobs/src/test/java/org/kie/kogito/jobs/management/springboot/SpringRestJobsServiceTest.java
@@ -22,7 +22,7 @@ import java.net.URI;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.kie.kogito.jobs.ProcessInstanceJobDescription;
+import org.kie.kogito.jobs.descriptors.ProcessInstanceJobDescription;
 import org.kie.kogito.jobs.management.RestJobsServiceTest;
 import org.kie.kogito.jobs.service.api.Job;
 import org.mockito.ArgumentCaptor;
@@ -63,7 +63,7 @@ class SpringRestJobsServiceTest extends RestJobsServiceTest<SpringRestJobsServic
     void scheduleProcessInstanceJob() throws Exception {
         when(restTemplate.postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok().build());
         ProcessInstanceJobDescription processInstanceJobDescription = buildProcessInstanceJobDescription();
-        tested.scheduleProcessInstanceJob(processInstanceJobDescription);
+        tested.scheduleJob(processInstanceJobDescription);
         ArgumentCaptor<HttpEntity<String>> jobArgumentCaptor = forClass(HttpEntity.class);
         verify(restTemplate).postForEntity(eq(tested.getJobsServiceUri()),
                 jobArgumentCaptor.capture(),
diff --git a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-filesystem/.gitignore b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-filesystem/.gitignore
index a180e83ba7..c2ca403117 100644
--- a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-filesystem/.gitignore
+++ b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-filesystem/.gitignore
@@ -19,3 +19,4 @@
 
 *.bpmn
 *.wid
+/target/
diff --git a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
index d1875057a9..1c65e780b9 100644
--- a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
+++ b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
@@ -18,13 +18,20 @@
  */
 package org.kie.kogito.process;
 
+import java.util.List;
+
 import org.kie.kogito.config.ConfigBean;
 import org.kie.kogito.correlation.CorrelationService;
 import org.kie.kogito.event.correlation.DefaultCorrelationService;
+import org.kie.kogito.jobs.JobsService;
 import org.kie.kogito.process.version.ProjectVersionProcessVersionResolver;
+import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
+import org.kie.kogito.services.jobs.impl.InMemoryJobService;
+import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
 import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
 import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
 import org.kie.kogito.uow.UnitOfWorkManager;
+import org.kie.kogito.usertask.UserTasks;
 import org.kogito.workitem.rest.RestWorkItemHandlerUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -50,6 +57,15 @@ public class KogitoBeanProducer {
         return new DefaultCorrelationService();
     }
 
+    @Bean
+    @ConditionalOnMissingBean(JobsService.class)
+    JobsService jobsService(List<Processes> processes, List<UserTasks> userTasks, UnitOfWorkManager uowm) {
+        InMemoryJobContext context = new InMemoryJobContext(null, uowm, !processes.isEmpty() ? processes.get(0) : null, !userTasks.isEmpty() ? userTasks.get(0) : null);
+        InMemoryJobService inMemoryJobService = new InMemoryJobService();
+        inMemoryJobService.registerJobExecutorFactory(new InMemoryProcessJobExecutorFactory(context));
+        return inMemoryJobService;
+    }
+
     @Bean
     @ConditionalOnProperty(value = "kogito.workflow.version-strategy", havingValue = "project")
     ProcessVersionResolver projectVersionResolver() {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@kie.apache.org
For additional commands, e-mail: commits-help@kie.apache.org