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/10/07 18:04:08 UTC

(incubator-kie-kogito-runtimes) branch main updated: [incubator-kie-issues-1484] Create endpoints for user task (#3673)

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 ad419c82f8 [incubator-kie-issues-1484] Create endpoints for user task (#3673)
ad419c82f8 is described below

commit ad419c82f8f4b45d99faacbe620a777b48a6af26
Author: Enrique <en...@ibm.com>
AuthorDate: Mon Oct 7 20:04:03 2024 +0200

    [incubator-kie-issues-1484] Create endpoints for user task (#3673)
---
 .../workitem/KogitoWorkItemHandlerFactory.java     |   2 +-
 .../usertask/event/KogitoUserTaskEventSupport.java |   3 +-
 .../kogito/process/workitem/TaskMetaEntity.java    |   4 +
 .../java/org/kie/kogito/usertask/UserTask.java     |   2 +
 ...rTasks.java => UserTaskAssignmentStrategy.java} |  14 +-
 ....java => UserTaskAssignmentStrategyConfig.java} |  13 +-
 .../org/kie/kogito/usertask/UserTaskConfig.java    |   4 +
 .../org/kie/kogito/usertask/UserTaskInstance.java  |  43 +-
 .../org/kie/kogito/usertask/UserTaskInstances.java |  13 +-
 .../org/kie/kogito/usertask/UserTaskService.java   |  64 +++
 .../java/org/kie/kogito/usertask/UserTasks.java    |   2 +
 .../kogito/usertask/events/UserTaskStateEvent.java |   6 +-
 .../usertask/lifecycle/UserTaskLifeCycle.java      |   6 +-
 .../kogito/usertask/lifecycle/UserTaskState.java   |   7 +-
 ...Token.java => UserTaskTransitionException.java} |   9 +-
 .../lifecycle/UserTaskTransitionExecutor.java      |   3 +-
 .../lifecycle/UserTaskTransitionToken.java         |   6 +-
 .../org/kie/kogito/usertask/model/Attachment.java  |   4 +
 .../org/kie/kogito/usertask/model/Comment.java     |   3 +
 .../{UserTasks.java => model/CommentInfo.java}     |  21 +-
 .../UserTaskTransitionView.java}                   |  37 +-
 .../org/kie/kogito/usertask/view/UserTaskView.java | 178 ++++++++
 .../event/impl/DefaultInstanceEventBatch.java      |   2 +-
 .../UserTaskStateEventDataEventAdapter.java        |   4 +-
 .../compiler/canonical/AbstractNodeVisitor.java    |   3 +-
 .../canonical/ProcessToExecModelGenerator.java     |  17 +-
 ...delMetaData.java => WorkItemModelMetaData.java} |  45 +-
 .../process/instance/LightWorkItemManager.java     |  23 +-
 .../instance/impl/WorkflowProcessInstanceImpl.java |  12 +
 .../instance/node/WorkItemNodeInstance.java        |   4 +-
 .../process/impl/AbstractProcessInstance.java      |  33 +-
 .../handler/UserTaskKogitoWorkItemHandler.java     | 186 ++------
 ...erTaskKogitoWorkItemHandlerProcessListener.java |  66 +++
 ...{DefaultUserTask.java => AbstractUserTask.java} |  61 +--
 .../impl/BasicUserTaskAssignmentStrategy.java      |  33 +-
 .../kie/kogito/usertask/impl/DefaultUserTask.java  | 265 +-----------
 .../DefaultUserTaskAssignmentStrategyConfig.java   |  54 +++
 .../usertask/impl/DefaultUserTaskConfig.java       |  30 +-
 .../impl/DefaultUserTaskEventListenerConfig.java   |   7 +-
 .../usertask/impl/DefaultUserTaskInstance.java     | 188 +++++---
 .../kie/kogito/usertask/impl/DefaultUserTasks.java |  34 ++
 .../usertask/impl/InMemoryUserTaskInstances.java   |  79 +++-
 .../impl/KogitoUserTaskEventSupportImpl.java       |   3 +-
 .../kogito/usertask/impl/UserTaskServiceImpl.java  | 265 ++++++++++++
 .../impl/events/UserTaskStateEventImpl.java        |  17 +-
 .../impl/lifecycle/DefaultUserTaskLifeCycle.java   | 153 +++++--
 .../lifecycle/DefaultUserTaskTransitionToken.java  |  22 +-
 .../impl/DefaultKogitoWorkItemHandler.java         |  28 +-
 .../kie/kogito/codegen/tests/PublishEventIT.java   |  22 +-
 .../org/kie/kogito/codegen/tests/UserTaskIT.java   | 478 ++++++++++-----------
 .../kie/kogito/codegen/process/ProcessCodegen.java |  27 +-
 .../codegen/process/ProcessResourceGenerator.java  |  44 +-
 ...rator.java => WorkItemModelClassGenerator.java} |  12 +-
 .../kogito/codegen/usertask/UserTaskCodegen.java   |  53 ++-
 .../codegen/usertask/UserTaskConfigGenerator.java  |   3 -
 .../RestResourceUserTaskQuarkusTemplate.java       | 266 ------------
 .../RestResourceUserTaskSpringTemplate.java        | 259 -----------
 .../RestResourceWorkItemQuarkusTemplate.java       | 139 ++++++
 .../RestResourceWorkItemSpringTemplate.java        | 137 ++++++
 .../RestResourceUserTaskQuarkusTemplate.java       | 248 +++++++++++
 .../RestResourceUserTaskSpringTemplate.java        | 210 +++++++++
 .../usertask/UserTaskConfigQuarkusTemplate.java    |  20 +-
 .../usertask/UserTaskConfigSpringTemplate.java     |  17 +-
 .../usertask/UserTaskQuarkusTemplate.java          |  16 +-
 .../usertask/UserTaskSpringTemplate.java           |  17 +-
 .../UserTasksContainerQuarkusTemplate.java         |  46 ++
 .../usertask/UserTasksContainerSpringTemplate.java |  53 ++-
 .../UserTasksServiceProducerQuarkusTemplate.java   |  68 +++
 .../UserTasksServiceProducerSpringTemplate.java    |  67 +++
 .../java/org/kie/kogito/it/PersistenceTest.java    |   2 +-
 .../src/main/resources/AddedTask.bpmn2             | 231 ++++++++++
 .../org/kie/kogito/it/InfinispanPersistenceIT.java |  75 ++++
 .../integrationtests/quarkus/AdHocFragmentsIT.java |   2 +-
 .../kogito/integrationtests/quarkus/TaskIT.java    |  97 ++---
 .../springboot/AdHocFragmentsTest.java             |   2 +-
 .../integrationtests/springboot/TaskTest.java      |  55 +--
 .../java/org/kie/kogito/it/PersistenceTest.java    |  27 +-
 77 files changed, 3085 insertions(+), 1686 deletions(-)

diff --git a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java
index 0b9438bb8c..307bbb2eed 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java
@@ -26,7 +26,7 @@ public interface KogitoWorkItemHandlerFactory {
 
     public static List<KogitoWorkItemHandler> findAllKogitoWorkItemHandlersRegistered() {
         List<KogitoWorkItemHandler> handlers = new ArrayList<>();
-        ServiceLoader.load(KogitoWorkItemHandlerFactory.class).stream()
+        ServiceLoader.load(KogitoWorkItemHandlerFactory.class, Thread.currentThread().getContextClassLoader()).stream()
                 .map(ServiceLoader.Provider<KogitoWorkItemHandlerFactory>::get)
                 .map(KogitoWorkItemHandlerFactory::provide)
                 .flatMap(List::stream)
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java b/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java
index 4adc903a21..5ec63f2704 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java
@@ -23,6 +23,7 @@ import java.util.Set;
 
 import org.kie.kogito.usertask.UserTaskEventListener;
 import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.model.Attachment;
 import org.kie.kogito.usertask.model.Comment;
 
@@ -37,7 +38,7 @@ public interface KogitoUserTaskEventSupport {
 
     void fireOneUserTaskStateChange(
             UserTaskInstance instance,
-            String oldPhaseStatus, String newPhaseStatus);
+            UserTaskState oldPhaseStatus, UserTaskState newPhaseStatus);
 
     void fireOnUserTaskNotStartedDeadline(
             UserTaskInstance instance,
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java b/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java
index 84f07f496a..49150a0af6 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java
@@ -57,6 +57,10 @@ public class TaskMetaEntity<K extends Serializable, T extends Serializable> impl
         return updatedBy;
     }
 
+    public void setId(K id) {
+        this.id = id;
+    }
+
     public K getId() {
         return id;
     }
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 8910256e68..11d762195a 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
@@ -109,4 +109,6 @@ public interface UserTask {
 
     Collection<DeadlineInfo<Reassignment>> getNotCompletedReassigments();
 
+    UserTaskAssignmentStrategy getAssignmentStrategy();
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java
similarity index 70%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java
index 3d31047cad..985a5d2557 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java
@@ -18,14 +18,18 @@
  */
 package org.kie.kogito.usertask;
 
-import java.util.Collection;
+import java.util.Optional;
 
-import org.kie.kogito.KogitoEngine;
+import org.kie.kogito.auth.IdentityProvider;
 
-public interface UserTasks extends KogitoEngine {
+public interface UserTaskAssignmentStrategy {
 
-    UserTask userTaskById(String userTaskId);
+    static final String DEFAULT_NAME = "default";
 
-    Collection<String> userTaskIds();
+    default String getName() {
+        return getClass().getName();
+    }
+
+    Optional<String> computeAssigment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider);
 
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java
similarity index 71%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java
index 3d31047cad..b9bc180a52 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java
@@ -18,14 +18,15 @@
  */
 package org.kie.kogito.usertask;
 
-import java.util.Collection;
+import java.util.List;
 
-import org.kie.kogito.KogitoEngine;
+public interface UserTaskAssignmentStrategyConfig {
 
-public interface UserTasks extends KogitoEngine {
+    List<UserTaskAssignmentStrategy> userTaskAssignmentStrategies();
 
-    UserTask userTaskById(String userTaskId);
-
-    Collection<String> userTaskIds();
+    UserTaskAssignmentStrategy forName(String name);
 
+    default UserTaskAssignmentStrategy defaultUserTaskAssignmentStrategy() {
+        return forName(UserTaskAssignmentStrategy.DEFAULT_NAME);
+    }
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java
index ee031f8418..2bebb31ef6 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java
@@ -28,6 +28,8 @@ public interface UserTaskConfig extends KogitoConfig {
 
     UserTaskEventListenerConfig userTaskEventListeners();
 
+    UserTaskAssignmentStrategyConfig userTaskAssignmentStrategies();
+
     UserTaskLifeCycle userTaskLifeCycle();
 
     UnitOfWorkManager unitOfWorkManager();
@@ -36,4 +38,6 @@ public interface UserTaskConfig extends KogitoConfig {
 
     IdentityProvider identityProvider();
 
+    UserTaskInstances userTaskInstances();
+
 }
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 9e73f14624..4a0023a0c4 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
@@ -22,8 +22,8 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 
+import org.kie.kogito.auth.IdentityProvider;
 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;
 
@@ -35,19 +35,15 @@ public interface UserTaskInstance {
 
     UserTaskState getStatus();
 
+    String getUserTaskId();
+
     boolean hasActualOwner();
 
-    void setActuaOwner(String string);
+    void setActuaOwner(String actualOwner);
 
     String getActualOwner();
 
-    UserTaskTransitionToken createTransitionToken(String transitionId, Map<String, Object> data);
-
-    void transition(UserTaskTransitionToken token);
-
-    void complete();
-
-    void abort();
+    void transition(String transitionId, Map<String, Object> data, IdentityProvider identityProvider);
 
     String getExternalReferenceId();
 
@@ -59,6 +55,14 @@ public interface UserTaskInstance {
 
     Map<String, Object> getMetadata();
 
+    Map<String, Object> getOutputs();
+
+    Map<String, Object> getInputs();
+
+    void setInput(String key, Object value);
+
+    void setOutput(String key, Object value);
+
     /**
      * Returns potential users that can work on this task
      * 
@@ -94,23 +98,24 @@ public interface UserTaskInstance {
      */
     Set<String> getExcludedUsers();
 
-    void addAttachment(Attachment attachment);
+    Attachment findAttachmentById(String attachmentId);
 
-    void updateAttachment(Attachment newAttachment);
+    Attachment addAttachment(Attachment attachment);
 
-    void removeAttachment(Attachment oldAttachment);
+    Attachment updateAttachment(Attachment newAttachment);
 
-    void addComment(Comment comment);
+    Attachment removeAttachment(Attachment oldAttachment);
 
-    void updateComment(Comment newComment);
+    Collection<Attachment> getAttachments();
 
-    void removeComment(Comment comment);
+    Comment findCommentById(String commentId);
 
-    Collection<Comment> getComments();
+    Comment addComment(Comment comment);
 
-    Collection<Attachment> getAttachments();
+    Comment updateComment(Comment newComment);
 
-    Attachment findAttachmentById(String attachmentId);
+    Comment removeComment(Comment comment);
+
+    Collection<Comment> getComments();
 
-    Comment findCommentById(String commentId);
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
index 57e956bd87..7f294b15fe 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
@@ -18,11 +18,20 @@
  */
 package org.kie.kogito.usertask;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
 
+import org.kie.kogito.auth.IdentityProvider;
+
 public interface UserTaskInstances {
 
+    void setReconnectUserTaskInstance(Function<UserTaskInstance, UserTaskInstance> reconnectUserTaskInstance);
+
+    void setDisconnectUserTaskInstance(Function<UserTaskInstance, UserTaskInstance> disconnectUserTaskInstance);
+
+    List<UserTaskInstance> findByIdentity(IdentityProvider identityProvider);
+
     Optional<UserTaskInstance> findById(String userTaskInstanceId);
 
     boolean exists(String userTaskInstanceId);
@@ -33,8 +42,4 @@ public interface UserTaskInstances {
 
     UserTaskInstance remove(String userTaskInstanceId);
 
-    void setReconnectUserTaskInstance(Function<UserTaskInstance, UserTaskInstance> reconnectUserTaskInstance);
-
-    void setDisconnectUserTaskInstance(Function<UserTaskInstance, UserTaskInstance> disconnectUserTaskInstance);
-
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java
new file mode 100644
index 0000000000..70cfdfbdc2
--- /dev/null
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.usertask;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.usertask.model.Attachment;
+import org.kie.kogito.usertask.model.Comment;
+import org.kie.kogito.usertask.view.UserTaskTransitionView;
+import org.kie.kogito.usertask.view.UserTaskView;
+
+public interface UserTaskService {
+
+    Optional<UserTaskView> getUserTaskInstance(String taskId, IdentityProvider identity);
+
+    List<UserTaskView> list(IdentityProvider identity);
+
+    Optional<UserTaskView> transition(String taskId, String transitionId, Map<String, Object> data, IdentityProvider identity);
+
+    List<UserTaskTransitionView> allowedTransitions(String taskId, IdentityProvider identity);
+
+    Optional<UserTaskView> setOutputs(String taskId, Map<String, Object> data, IdentityProvider identity);
+
+    Optional<UserTaskView> setInputs(String taskId, Map<String, Object> data, IdentityProvider identity);
+
+    List<Comment> getComments(String taskId, IdentityProvider identity);
+
+    Optional<Comment> getComment(String taskId, String commentId, IdentityProvider identity);
+
+    Optional<Comment> addComment(String taskId, Comment comment, IdentityProvider identity);
+
+    Optional<Comment> updateComment(String taskId, Comment comment, IdentityProvider identity);
+
+    Optional<Comment> removeComment(String taskId, String commentId, IdentityProvider identity);
+
+    List<Attachment> getAttachments(String taskId, IdentityProvider identity);
+
+    Optional<Attachment> getAttachment(String taskId, String commentId, IdentityProvider identity);
+
+    Optional<Attachment> addAttachment(String taskId, Attachment comment, IdentityProvider identity);
+
+    Optional<Attachment> updateAttachment(String taskId, Attachment comment, IdentityProvider identity);
+
+    Optional<Attachment> removeAttachment(String taskId, String commentId, IdentityProvider identity);
+}
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
index 3d31047cad..0253644c0f 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
@@ -24,6 +24,8 @@ import org.kie.kogito.KogitoEngine;
 
 public interface UserTasks extends KogitoEngine {
 
+    UserTaskInstances instances();
+
     UserTask userTaskById(String userTaskId);
 
     Collection<String> userTaskIds();
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java
index 196b76aafb..e5471cc96f 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java
@@ -19,10 +19,12 @@
 
 package org.kie.kogito.usertask.events;
 
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+
 public interface UserTaskStateEvent extends UserTaskEvent {
 
-    String getNewStatus();
+    UserTaskState getNewStatus();
 
-    String getOldStatus();
+    UserTaskState getOldStatus();
 
 }
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 eb8290884a..09170f4d1c 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
@@ -19,14 +19,16 @@
 
 package org.kie.kogito.usertask.lifecycle;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
+import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.usertask.UserTaskInstance;
 
 public interface UserTaskLifeCycle {
 
-    Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition);
+    Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition, IdentityProvider identity);
 
     UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map<String, Object> data);
 
@@ -34,4 +36,6 @@ public interface UserTaskLifeCycle {
 
     UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> emptyMap);
 
+    List<UserTaskTransition> allowedTransitions(UserTaskInstance ut);
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java
index 71ba89b94d..63a3242ede 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java
@@ -19,7 +19,6 @@
 package org.kie.kogito.usertask.lifecycle;
 
 import java.util.Objects;
-import java.util.Optional;
 
 public class UserTaskState {
 
@@ -69,8 +68,8 @@ public class UserTaskState {
         return name;
     }
 
-    public Optional<TerminationType> isTerminate() {
-        return Optional.ofNullable(terminate);
+    public boolean isTerminate() {
+        return terminate != null;
     }
 
     @Override
@@ -91,7 +90,7 @@ public class UserTaskState {
     }
 
     public static UserTaskState initalized() {
-        return of(null);
+        return of("Created");
     }
 
     @Override
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java
similarity index 79%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java
index db1dd9e8d7..3e98d19bdc 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java
@@ -18,11 +18,12 @@
  */
 package org.kie.kogito.usertask.lifecycle;
 
-import java.util.Map;
+public class UserTaskTransitionException extends RuntimeException {
 
-public interface UserTaskTransitionToken {
+    private static final long serialVersionUID = -9127576077607542859L;
 
-    UserTaskTransition transition();
+    public UserTaskTransitionException(String msg) {
+        super(msg);
+    }
 
-    Map<String, Object> data();
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java
index 6124901d28..79eb36a5a0 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java
@@ -20,10 +20,11 @@ package org.kie.kogito.usertask.lifecycle;
 
 import java.util.Optional;
 
+import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.usertask.UserTaskInstance;
 
 public interface UserTaskTransitionExecutor {
 
-    Optional<UserTaskTransitionToken> execute(UserTaskInstance transition, UserTaskTransitionToken token);
+    Optional<UserTaskTransitionToken> execute(UserTaskInstance transition, UserTaskTransitionToken token, IdentityProvider identity);
 
 }
\ No newline at end of file
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java
index db1dd9e8d7..8277f4d6d5 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java
@@ -22,7 +22,11 @@ import java.util.Map;
 
 public interface UserTaskTransitionToken {
 
-    UserTaskTransition transition();
+    String transitionId();
+
+    UserTaskState source();
+
+    UserTaskState target();
 
     Map<String, Object> data();
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java
index 04ff75b130..d032eca9ea 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java
@@ -27,6 +27,10 @@ public class Attachment extends TaskMetaEntity<String, URI> {
     private static final long serialVersionUID = 1L;
     private String name;
 
+    public Attachment() {
+
+    }
+
     public Attachment(String id, String user) {
         super(id, user);
     }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java
index 74a801b711..fe2a3fdf45 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java
@@ -24,6 +24,9 @@ public class Comment extends TaskMetaEntity<String, String> {
 
     private static final long serialVersionUID = -9106249675352498780L;
 
+    public Comment() {
+    }
+
     public Comment(String id, String user) {
         super(id, user);
     }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java
similarity index 69%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java
index 3d31047cad..5cbfd1404b 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java
@@ -16,16 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.usertask;
+package org.kie.kogito.usertask.model;
 
-import java.util.Collection;
+public class CommentInfo {
 
-import org.kie.kogito.KogitoEngine;
+    private String comment;
 
-public interface UserTasks extends KogitoEngine {
+    public CommentInfo() {
 
-    UserTask userTaskById(String userTaskId);
+    }
 
-    Collection<String> userTaskIds();
+    public CommentInfo(String comment) {
+        this.comment = comment;
+    }
 
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java
similarity index 54%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java
copy to api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java
index 04ff75b130..3f22f091e7 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java
@@ -16,31 +16,38 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.usertask.model;
+package org.kie.kogito.usertask.view;
 
-import java.net.URI;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
 
-import org.kie.kogito.process.workitem.TaskMetaEntity;
+public class UserTaskTransitionView {
 
-public class Attachment extends TaskMetaEntity<String, URI> {
+    private String transitionId;
+    private UserTaskState source;
+    private UserTaskState target;
 
-    private static final long serialVersionUID = 1L;
-    private String name;
+    public String getTransitionId() {
+        return transitionId;
+    }
+
+    public void setTransitionId(String transitionId) {
+        this.transitionId = transitionId;
+    }
 
-    public Attachment(String id, String user) {
-        super(id, user);
+    public UserTaskState getSource() {
+        return source;
     }
 
-    public String getName() {
-        return name;
+    public void setSource(UserTaskState source) {
+        this.source = source;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public UserTaskState getTarget() {
+        return target;
     }
 
-    @Override
-    public Attachment clone() throws CloneNotSupportedException {
-        return (Attachment) super.clone();
+    public void setTarget(UserTaskState target) {
+        this.target = target;
     }
+
 }
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java
new file mode 100644
index 0000000000..660ea4c844
--- /dev/null
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java
@@ -0,0 +1,178 @@
+/*
+ * 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.usertask.view;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+
+public class UserTaskView {
+
+    private String id;
+
+    private String userTaskId;
+
+    private UserTaskState status;
+
+    private String taskName;
+    private String taskDescription;
+    private Integer taskPriority;
+    private Set<String> potentialUsers;
+    private Set<String> potentialGroups;
+    private Set<String> adminUsers;
+    private Set<String> adminGroups;
+    private Set<String> excludedUsers;
+    private String externalReferenceId;
+    private String actualOwner;
+
+    private Map<String, Object> inputs;
+    private Map<String, Object> outputs;
+
+    private Map<String, Object> metadata;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUserTaskId() {
+        return userTaskId;
+    }
+
+    public void setUserTaskId(String userTaskId) {
+        this.userTaskId = userTaskId;
+    }
+
+    public UserTaskState getStatus() {
+        return status;
+    }
+
+    public void setStatus(UserTaskState status) {
+        this.status = status;
+    }
+
+    public String getTaskName() {
+        return taskName;
+    }
+
+    public void setTaskName(String taskName) {
+        this.taskName = taskName;
+    }
+
+    public String getTaskDescription() {
+        return taskDescription;
+    }
+
+    public void setTaskDescription(String taskDescription) {
+        this.taskDescription = taskDescription;
+    }
+
+    public Integer getTaskPriority() {
+        return taskPriority;
+    }
+
+    public void setTaskPriority(Integer taskPriority) {
+        this.taskPriority = taskPriority;
+    }
+
+    public Set<String> getPotentialUsers() {
+        return potentialUsers;
+    }
+
+    public void setPotentialUsers(Set<String> potentialUsers) {
+        this.potentialUsers = potentialUsers;
+    }
+
+    public Set<String> getPotentialGroups() {
+        return potentialGroups;
+    }
+
+    public void setPotentialGroups(Set<String> potentialGroups) {
+        this.potentialGroups = potentialGroups;
+    }
+
+    public Set<String> getAdminUsers() {
+        return adminUsers;
+    }
+
+    public void setAdminUsers(Set<String> adminUsers) {
+        this.adminUsers = adminUsers;
+    }
+
+    public Set<String> getAdminGroups() {
+        return adminGroups;
+    }
+
+    public void setAdminGroups(Set<String> adminGroups) {
+        this.adminGroups = adminGroups;
+    }
+
+    public Set<String> getExcludedUsers() {
+        return excludedUsers;
+    }
+
+    public void setExcludedUsers(Set<String> excludedUsers) {
+        this.excludedUsers = excludedUsers;
+    }
+
+    public String getExternalReferenceId() {
+        return externalReferenceId;
+    }
+
+    public void setExternalReferenceId(String externalReferenceId) {
+        this.externalReferenceId = externalReferenceId;
+    }
+
+    public String getActualOwner() {
+        return actualOwner;
+    }
+
+    public void setActualOwner(String actualOwner) {
+        this.actualOwner = actualOwner;
+    }
+
+    public Map<String, Object> getInputs() {
+        return inputs;
+    }
+
+    public void setInputs(Map<String, Object> inputs) {
+        this.inputs = inputs;
+    }
+
+    public Map<String, Object> getOutputs() {
+        return outputs;
+    }
+
+    public void setOutputs(Map<String, Object> outputs) {
+        this.outputs = outputs;
+    }
+
+    public Map<String, Object> getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+
+}
diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java
index c4f6cd76b2..df94b1183a 100644
--- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java
+++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java
@@ -68,7 +68,7 @@ public class DefaultInstanceEventBatch implements EventBatch {
 
     @Override
     public void append(Object event) {
-        LOG.info("event generated {}", event);
+        LOG.trace("event generated {}", event);
         this.dataEventAdapters.stream().filter(a -> a.accept(event)).map(a -> a.adapt(event)).forEach(this.processedEvents::add);
     }
 
diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java
index fe577ac0c2..af70b37ae3 100644
--- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java
+++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java
@@ -54,9 +54,9 @@ public class UserTaskStateEventDataEventAdapter extends AbstractDataEventAdapter
                 .userTaskDescription(event.getUserTaskInstance().getTaskDescription())
                 .userTaskPriority(priorityStr)
                 .userTaskReferenceName(event.getUserTask().getReferenceName())
-                .state(event.getNewStatus())
+                .state(event.getNewStatus().getName())
                 .actualOwner(event.getUserTaskInstance().getActualOwner())
-                .eventType(isTransition(event) ? event.getNewStatus() : "Modify")
+                .eventType(isTransition(event) ? event.getNewStatus().getName() : "Modify")
                 .processInstanceId((String) event.getUserTaskInstance().getMetadata().get("ProcessInstanceId"));
 
         UserTaskInstanceStateEventBody body = builder.build();
diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java
index a8ce5a76b8..1946318d05 100644
--- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java
+++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java
@@ -53,7 +53,6 @@ import org.jbpm.workflow.core.impl.DroolsConsequenceAction;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.jbpm.workflow.core.impl.NodeImpl;
 import org.jbpm.workflow.core.node.Assignment;
-import org.jbpm.workflow.core.node.HumanTaskNode;
 import org.jbpm.workflow.core.node.StartNode;
 import org.jbpm.workflow.core.node.Transformation;
 import org.kie.api.definition.process.Connection;
@@ -117,7 +116,7 @@ public abstract class AbstractNodeVisitor<T extends Node> extends AbstractVisito
 
     public void visitNode(T node, BlockStmt body, VariableScope variableScope, ProcessMetaData metadata) {
         visitNode(FACTORY_FIELD_NAME, node, body, variableScope, metadata);
-        if (isAdHocNode(node) && !(node instanceof HumanTaskNode)) {
+        if (isAdHocNode(node)) {
             metadata.addSignal(node.getName(), null);
         }
         if (isExtendedNode(node)) {
diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
index 253d24a7f0..678c465733 100644
--- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
+++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
@@ -29,7 +29,7 @@ import org.jbpm.process.core.ContextContainer;
 import org.jbpm.process.core.context.variable.Variable;
 import org.jbpm.process.core.context.variable.VariableScope;
 import org.jbpm.workflow.core.impl.WorkflowProcessImpl;
-import org.jbpm.workflow.core.node.HumanTaskNode;
+import org.jbpm.workflow.core.node.WorkItemNode;
 import org.kie.api.definition.process.Node;
 import org.kie.api.definition.process.WorkflowProcess;
 import org.kie.kogito.ProcessInput;
@@ -243,27 +243,26 @@ public class ProcessToExecModelGenerator {
         return sanitizeClassName(extractProcessId(processId) + MODEL_CLASS_SUFFIX);
     }
 
-    public List<UserTaskModelMetaData> generateUserTaskModel(WorkflowProcess process) {
+    public List<WorkItemModelMetaData> generateWorkItemModel(WorkflowProcess process) {
         String packageName = process.getPackageName();
-        List<UserTaskModelMetaData> userTaskModels = new ArrayList<>();
+        List<WorkItemModelMetaData> workItemTaskModels = new ArrayList<>();
 
         VariableScope variableScope = (VariableScope) ((org.jbpm.process.core.Process) process).getDefaultContext(
                 VariableScope.VARIABLE_SCOPE);
 
         for (Node node : ((WorkflowProcessImpl) process).getNodesRecursively()) {
-            if (node instanceof HumanTaskNode) {
-                HumanTaskNode humanTaskNode = (HumanTaskNode) node;
-                VariableScope nodeVariableScope = (VariableScope) ((ContextContainer) humanTaskNode
+            if (node instanceof WorkItemNode workItemNode) {
+                VariableScope nodeVariableScope = (VariableScope) ((ContextContainer) workItemNode
                         .getParentContainer()).getDefaultContext(VariableScope.VARIABLE_SCOPE);
                 if (nodeVariableScope == null) {
                     nodeVariableScope = variableScope;
                 }
-                userTaskModels.add(new UserTaskModelMetaData(packageName, variableScope, nodeVariableScope,
-                        humanTaskNode, process.getId()));
+                workItemTaskModels.add(new WorkItemModelMetaData(packageName, variableScope, nodeVariableScope,
+                        workItemNode, process.getId()));
             }
         }
 
-        return userTaskModels;
+        return workItemTaskModels;
     }
 
     public static String extractProcessId(String processId) {
diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
similarity index 90%
rename from jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java
rename to jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
index 3d648ad2e2..5ae7418790 100644
--- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java
+++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
@@ -30,7 +30,7 @@ import org.jbpm.process.core.context.variable.VariableScope;
 import org.jbpm.process.core.datatype.DataType;
 import org.jbpm.process.core.datatype.DataTypeResolver;
 import org.jbpm.util.PatternConstants;
-import org.jbpm.workflow.core.node.HumanTaskNode;
+import org.jbpm.workflow.core.node.WorkItemNode;
 import org.kie.api.definition.process.WorkflowElementIdentifier;
 import org.kie.kogito.UserTask;
 import org.kie.kogito.UserTaskParam;
@@ -70,7 +70,7 @@ import static com.github.javaparser.StaticJavaParser.parseClassOrInterfaceType;
 import static org.jbpm.ruleflow.core.Metadata.CUSTOM_AUTO_START;
 import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName;
 
-public class UserTaskModelMetaData {
+public class WorkItemModelMetaData {
 
     private static final String TASK_INTPUT_CLASS_SUFFIX = "TaskInput";
     private static final String TASK_OUTTPUT_CLASS_SUFFIX = "TaskOutput";
@@ -86,7 +86,7 @@ public class UserTaskModelMetaData {
 
     private final VariableScope processVariableScope;
     private final VariableScope variableScope;
-    private final HumanTaskNode humanTaskNode;
+    private final WorkItemNode workItemNode;
     private final String processId;
 
     private String inputModelClassName;
@@ -98,20 +98,20 @@ public class UserTaskModelMetaData {
     private String taskModelClassName;
     private String taskModelClassSimpleName;
 
-    public UserTaskModelMetaData(String packageName, VariableScope processVariableScope, VariableScope variableScope, HumanTaskNode humanTaskNode, String processId) {
+    public WorkItemModelMetaData(String packageName, VariableScope processVariableScope, VariableScope variableScope, WorkItemNode workItemNode, String processId) {
         this.packageName = packageName;
         this.processVariableScope = processVariableScope;
         this.variableScope = variableScope;
-        this.humanTaskNode = humanTaskNode;
+        this.workItemNode = workItemNode;
         this.processId = processId;
 
-        this.inputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_INTPUT_CLASS_SUFFIX);
+        this.inputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_INTPUT_CLASS_SUFFIX);
         this.inputModelClassName = packageName + '.' + inputModelClassSimpleName;
 
-        this.outputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_OUTTPUT_CLASS_SUFFIX);
+        this.outputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_OUTTPUT_CLASS_SUFFIX);
         this.outputModelClassName = packageName + '.' + outputModelClassSimpleName;
 
-        this.taskModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_MODEL_CLASS_SUFFIX);
+        this.taskModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_MODEL_CLASS_SUFFIX);
         this.taskModelClassName = packageName + '.' + taskModelClassSimpleName;
 
     }
@@ -144,21 +144,21 @@ public class UserTaskModelMetaData {
     }
 
     public String getName() {
-        return (String) humanTaskNode.getWork().getParameters().getOrDefault(TASK_NAME, humanTaskNode.getName());
+        return (String) workItemNode.getWork().getParameters().getOrDefault(TASK_NAME, workItemNode.getName());
     }
 
     public String getNodeName() {
-        return humanTaskNode.getName();
+        return workItemNode.getName();
     }
 
     public WorkflowElementIdentifier getId() {
-        return humanTaskNode.getId();
+        return workItemNode.getId();
     }
 
     private void addUserTaskAnnotation(ClassOrInterfaceDeclaration modelClass) {
-        String taskName = (String) humanTaskNode.getWork().getParameter(TASK_NAME);
+        String taskName = (String) workItemNode.getWork().getParameter(TASK_NAME);
         if (taskName == null)
-            taskName = humanTaskNode.getName();
+            taskName = workItemNode.getName();
         modelClass.addAndGetAnnotation(UserTask.class).addPair("taskName", new StringLiteralExpr(taskName)).addPair("processName", new StringLiteralExpr(processId));
     }
 
@@ -190,8 +190,8 @@ public class UserTaskModelMetaData {
         NameExpr item = new NameExpr("item");
 
         // map is task input -> context variable / process variable
-        Map<String, String> inputTypes = humanTaskNode.getIoSpecification().getInputTypes();
-        for (Entry<String, String> entry : humanTaskNode.getIoSpecification().getInputMapping().entrySet()) {
+        Map<String, String> inputTypes = workItemNode.getIoSpecification().getInputTypes();
+        for (Entry<String, String> entry : workItemNode.getIoSpecification().getInputMapping().entrySet()) {
             if (INTERNAL_FIELDS.contains(entry.getKey())) {
                 continue;
             }
@@ -235,7 +235,7 @@ public class UserTaskModelMetaData {
                     AssignExpr.Operator.ASSIGN));
         }
 
-        for (Entry<String, Object> entry : humanTaskNode.getWork().getParameters().entrySet()) {
+        for (Entry<String, Object> entry : workItemNode.getWork().getParameters().entrySet()) {
 
             if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) {
                 continue;
@@ -275,7 +275,6 @@ public class UserTaskModelMetaData {
         return compilationUnit;
     }
 
-    @SuppressWarnings({ "unchecked" })
     private CompilationUnit compilationUnitOutput() {
         CompilationUnit compilationUnit = parse(this.getClass().getResourceAsStream("/class-templates/TaskOutputTemplate.java"));
         compilationUnit.setPackageDeclaration(packageName);
@@ -303,8 +302,8 @@ public class UserTaskModelMetaData {
                 HashMap.class.getSimpleName() + "<>"), NodeList.nodeList()), AssignExpr.Operator.ASSIGN));
 
         // map is task output -> context variable / process variable
-        Map<String, String> outputTypes = humanTaskNode.getIoSpecification().getOutputTypes();
-        for (Entry<String, String> entry : humanTaskNode.getIoSpecification().getOutputMappingBySources().entrySet()) {
+        Map<String, String> outputTypes = workItemNode.getIoSpecification().getOutputTypes();
+        for (Entry<String, String> entry : workItemNode.getIoSpecification().getOutputMappingBySources().entrySet()) {
             if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) {
                 continue;
             }
@@ -382,7 +381,7 @@ public class UserTaskModelMetaData {
     }
 
     private void addComment(CompilationUnit unit, String prefix) {
-        unit.addOrphanComment(new LineComment(prefix + " for user task '" + humanTaskNode.getName() + "' in process '" + processId + "'"));
+        unit.addOrphanComment(new LineComment(prefix + " for user task '" + workItemNode.getName() + "' in process '" + processId + "'"));
     }
 
     private void templateReplacement(NameExpr name) {
@@ -407,13 +406,13 @@ public class UserTaskModelMetaData {
     }
 
     public boolean isAdHoc() {
-        return !Boolean.parseBoolean((String) humanTaskNode.getMetaData(CUSTOM_AUTO_START))
-                && (humanTaskNode.getIncomingConnections() == null || humanTaskNode.getIncomingConnections().isEmpty());
+        return !Boolean.parseBoolean((String) workItemNode.getMetaData(CUSTOM_AUTO_START))
+                && (workItemNode.getIncomingConnections() == null || workItemNode.getIncomingConnections().isEmpty());
     }
 
     public SwitchEntry getModelSwitchEntry() {
         SwitchEntry entry = new SwitchEntry();
-        entry.setLabels(NodeList.nodeList(new StringLiteralExpr(humanTaskNode.getId().toExternalFormat())));
+        entry.setLabels(NodeList.nodeList(new StringLiteralExpr(workItemNode.getId().toExternalFormat())));
         entry.addStatement(new ReturnStmt(new MethodCallExpr(new NameExpr(
                 taskModelClassSimpleName), new SimpleName("from")).addArgument(WORK_ITEM)));
         return entry;
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
index 16a1519fe4..01b2410465 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
@@ -74,8 +74,12 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
     }
 
     @Override
-    public InternalKogitoWorkItem getWorkItem(String id) {
-        return workItems.get(id);
+    public InternalKogitoWorkItem getWorkItem(String workItemId) {
+        InternalKogitoWorkItem workItem = workItems.get(workItemId);
+        if (workItem == null) {
+            throw new WorkItemNotFoundException("Work Item (" + workItemId + ") does not exist", workItemId);
+        }
+        return workItem;
     }
 
     @Override
@@ -131,7 +135,6 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
         KogitoWorkItemHandler handler = getWorkItemHandler(workItem);
         WorkItemTransition transition = handler.startingTransition(Collections.emptyMap());
         transitionWorkItem(workItem, transition, true);
-
     }
 
     @Override
@@ -143,7 +146,6 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
         KogitoProcessInstance processInstance = processInstanceManager.getProcessInstance(workItem.getProcessInstanceStringId());
         workItem.setState(KogitoWorkItem.COMPLETED);
         processInstance.signalEvent("workItemCompleted", workItem);
-
     }
 
     @Override
@@ -160,10 +162,7 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
 
     @Override
     public <T> T updateWorkItem(String workItemId, Function<KogitoWorkItem, T> updater, Policy... policies) {
-        InternalKogitoWorkItem workItem = workItems.get(workItemId);
-        if (workItem == null) {
-            throw new WorkItemNotFoundException(workItemId);
-        }
+        InternalKogitoWorkItem workItem = getWorkItem(workItemId);
         Stream.of(policies).forEach(p -> p.enforce(workItem));
         T results = updater.apply(workItem);
         return results;
@@ -178,10 +177,7 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
 
     @Override
     public void transitionWorkItem(String workItemId, WorkItemTransition transition) {
-        InternalKogitoWorkItem workItem = workItems.get(workItemId);
-        if (workItem == null) {
-            throw new WorkItemNotFoundException("Work Item (" + workItemId + ") does not exist", workItemId);
-        }
+        InternalKogitoWorkItem workItem = getWorkItem(workItemId);
         transitionWorkItem(workItem, transition, true);
     }
 
@@ -209,7 +205,7 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
         }
 
         if (lastTransition.termination().isPresent()) {
-            internalRemoveWorkItem(workItem.getStringId());
+
             if (signal) {
                 switch (lastTransition.termination().get()) {
                     case COMPLETE:
@@ -222,6 +218,7 @@ public class LightWorkItemManager implements InternalKogitoWorkItemManager {
                         break;
                 }
             }
+
         }
     }
 
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 7ae6edf3fb..93554930fa 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
@@ -76,6 +76,7 @@ import org.jbpm.workflow.instance.node.EventNodeInstanceInterface;
 import org.jbpm.workflow.instance.node.EventSubProcessNodeInstance;
 import org.jbpm.workflow.instance.node.FaultNodeInstance;
 import org.jbpm.workflow.instance.node.StateBasedNodeInstance;
+import org.jbpm.workflow.instance.node.WorkItemNodeInstance;
 import org.kie.api.definition.process.NodeContainer;
 import org.kie.api.definition.process.WorkflowElementIdentifier;
 import org.kie.api.runtime.rule.AgendaFilter;
@@ -464,6 +465,11 @@ public abstract class WorkflowProcessInstanceImpl extends ProcessInstanceImpl im
 
     @Override
     public void disconnect() {
+        for (org.kie.api.runtime.process.NodeInstance nodeInstance : getNodeInstances(true)) {
+            if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) {
+                workItemNodeInstance.internalRemoveWorkItem();
+            }
+        }
         removeEventListeners();
         unregisterExternalEventNodeListeners();
 
@@ -478,11 +484,17 @@ public abstract class WorkflowProcessInstanceImpl extends ProcessInstanceImpl im
     @Override
     public void reconnect() {
         super.reconnect();
+        for (org.kie.api.runtime.process.NodeInstance nodeInstance : getNodeInstances(true)) {
+            if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) {
+                workItemNodeInstance.internalRegisterWorkItem();
+            }
+        }
         for (NodeInstance nodeInstance : nodeInstances) {
             if (nodeInstance instanceof EventBasedNodeInstanceInterface) {
                 ((EventBasedNodeInstanceInterface) nodeInstance).addEventListeners();
             }
         }
+
         registerExternalEventNodeListeners();
     }
 
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
index 5af0a5de03..be1ff4ddfa 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
@@ -274,7 +274,7 @@ public class WorkItemNodeInstance extends StateBasedNodeInstance implements Even
         if (workItemNode != null && workItem.getState() == COMPLETED) {
             validateWorkItemResultVariable(getProcessInstance().getProcessName(), workItemNode.getOutAssociations(), workItem);
             Map<String, Object> outputs = new HashMap<>(workItem.getResults());
-            if (workItem.getActualOwner() != null) {
+            if (workItem.getActualOwner() != null && !outputs.containsKey("ActorId")) {
                 outputs.put("ActorId", workItem.getActualOwner());
             }
             NodeIoHelper.processOutputs(this, varRef -> outputs.get(varRef), varName -> this.getVariable(varName));
@@ -284,6 +284,8 @@ public class WorkItemNodeInstance extends StateBasedNodeInstance implements Even
             setMetaData("NodeType", workItem.getName());
             mapDynamicOutputData(workItem.getResults());
         }
+
+        internalRemoveWorkItem();
         if (isInversionOfControl()) {
             KieRuntime kruntime = getProcessInstance().getKnowledgeRuntime();
             kruntime.update(kruntime.getFactHandle(this), this);
diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
index 39d2937465..99f32900da 100644
--- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
@@ -73,9 +73,13 @@ import org.kie.kogito.process.flexible.AdHocFragment;
 import org.kie.kogito.process.flexible.Milestone;
 import org.kie.kogito.process.workitems.InternalKogitoWorkItem;
 import org.kie.kogito.services.uow.ProcessInstanceWorkUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public abstract class AbstractProcessInstance<T extends Model> implements ProcessInstance<T> {
 
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessInstance.class);
+
     private static final String KOGITO_PROCESS_INSTANCE = "KogitoProcessInstance";
 
     protected final T variables;
@@ -156,6 +160,7 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
     }
 
     protected void reconnect() {
+        LOG.debug("reconnect process {}", processInstance.getId());
         //set correlation
         if (correlationInstance.isEmpty()) {
             correlationInstance = process().correlations().findByCorrelatedId(id());
@@ -169,12 +174,6 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
         processInstance.setMetaData(KOGITO_PROCESS_INSTANCE, this);
         addCompletionEventListener();
 
-        for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance.getNodeInstances()) {
-            if (nodeInstance instanceof WorkItemNodeInstance) {
-                ((WorkItemNodeInstance) nodeInstance).internalRegisterWorkItem();
-            }
-        }
-
         unbind(variables, processInstance.getVariables());
     }
 
@@ -193,15 +192,12 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
     }
 
     protected void disconnect() {
+
         if (processInstance == null) {
             return;
         }
 
-        for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance.getNodeInstances()) {
-            if (nodeInstance instanceof WorkItemNodeInstance) {
-                ((WorkItemNodeInstance) nodeInstance).internalRemoveWorkItem();
-            }
-        }
+        LOG.debug("disconnect process {}", processInstance.getId());
 
         processInstance.disconnect();
         processInstance.setMetaData(KOGITO_PROCESS_INSTANCE, null);
@@ -248,6 +244,7 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
         if (processInstance.getKnowledgeRuntime() != null) {
             disconnect();
         }
+
         processInstance = null;
     }
 
@@ -337,7 +334,7 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
 
     @Override
     public int status() {
-        return this.status;
+        return status;
     }
 
     @Override
@@ -556,12 +553,14 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
 
     @Override
     public void completeWorkItem(String id, Map<String, Object> variables, Policy... policies) {
+        syncWorkItems();
         getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().completeWorkItem(id, variables, policies);
         removeOnFinish();
     }
 
     @Override
     public <R> R updateWorkItem(String id, Function<KogitoWorkItem, R> updater, Policy... policies) {
+        syncWorkItems();
         R result = getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().updateWorkItem(id, updater, policies);
         addToUnitOfWork(pi -> ((MutableProcessInstances<T>) process.instances()).update(pi.id(), pi));
         return result;
@@ -569,16 +568,26 @@ public abstract class AbstractProcessInstance<T extends Model> implements Proces
 
     @Override
     public void abortWorkItem(String id, Policy... policies) {
+        syncWorkItems();
         getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().abortWorkItem(id, policies);
         removeOnFinish();
     }
 
     @Override
     public void transitionWorkItem(String id, WorkItemTransition transition) {
+        syncWorkItems();
         getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().transitionWorkItem(id, transition);
         removeOnFinish();
     }
 
+    private void syncWorkItems() {
+        for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance().getNodeInstances(true)) {
+            if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) {
+                workItemNodeInstance.internalRegisterWorkItem();
+            }
+        }
+    }
+
     @Override
     public Set<EventDescription<?>> events() {
         return processInstance().getEventDescriptions();
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
index 4fb06024d3..d612a69296 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
@@ -18,31 +18,27 @@
  */
 package org.kie.kogito.jbpm.usertask.handler;
 
-import java.util.Map;
+import java.util.Collections;
 import java.util.Optional;
 import java.util.Set;
 
-import org.kie.kogito.auth.SecurityPolicy;
-import org.kie.kogito.internal.process.workitem.InvalidTransitionException;
+import org.kie.kogito.Application;
+import org.kie.kogito.auth.IdentityProviders;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
-import org.kie.kogito.internal.process.workitem.Policy;
-import org.kie.kogito.internal.process.workitem.WorkItemLifeCycle;
-import org.kie.kogito.internal.process.workitem.WorkItemLifeCyclePhase;
-import org.kie.kogito.internal.process.workitem.WorkItemPhaseState;
-import org.kie.kogito.internal.process.workitem.WorkItemTerminationType;
 import org.kie.kogito.internal.process.workitem.WorkItemTransition;
 import org.kie.kogito.process.workitems.InternalKogitoWorkItem;
 import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler;
-import org.kie.kogito.process.workitems.impl.DefaultWorkItemLifeCycle;
-import org.kie.kogito.process.workitems.impl.DefaultWorkItemLifeCyclePhase;
 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 static java.util.Collections.emptyMap;
 import static java.util.Optional.ofNullable;
+import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.PARAMETER_NOTIFY;
+import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.WORKFLOW_ENGINE_USER;
 
 /**
  * Default Work Item handler based on the standard life cycle
@@ -51,29 +47,6 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
 
     private static String UT_SEPARATOR = System.getProperty("org.jbpm.ht.user.separator", ",");
 
-    public static final WorkItemPhaseState INACTIVE = WorkItemPhaseState.initialized();
-    public static final WorkItemPhaseState COMPLETED = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE);
-    public static final WorkItemPhaseState ABORTED = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT);
-    public static final WorkItemPhaseState ACTIVATED = WorkItemPhaseState.of("Activated");
-    public static final WorkItemPhaseState RESERVED = WorkItemPhaseState.of("Reserved");
-
-    public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_COMPLETE =
-            new DefaultWorkItemLifeCyclePhase("complete", RESERVED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_COMPLETE =
-            new DefaultWorkItemLifeCyclePhase("complete", ACTIVATED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteFromActiveWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_ABORT =
-            new DefaultWorkItemLifeCyclePhase("abort", RESERVED, ABORTED, UserTaskKogitoWorkItemHandler::userTaskAbortWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_ABORT =
-            new DefaultWorkItemLifeCyclePhase("abort", ACTIVATED, ABORTED, UserTaskKogitoWorkItemHandler::userTaskAbortWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_CLAIM =
-            new DefaultWorkItemLifeCyclePhase("claim", ACTIVATED, RESERVED, UserTaskKogitoWorkItemHandler::userTaskClaimWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_CREATED_ACTIVE =
-            new DefaultWorkItemLifeCyclePhase("activate", INACTIVE, ACTIVATED, UserTaskKogitoWorkItemHandler::userTaskActivateWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_RELEASE =
-            new DefaultWorkItemLifeCyclePhase("release", RESERVED, ACTIVATED, UserTaskKogitoWorkItemHandler::userTaskReleaseWorkItemHandler);
-    public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_COMPLETED =
-            new DefaultWorkItemLifeCyclePhase("skip", ACTIVATED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteWorkItemHandler);
-
     private static final String DESCRIPTION = "Description";
     private static final String PRIORITY = "Priority";
     private static final String TASK_NAME = "TaskName";
@@ -83,40 +56,16 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
     private static final String BUSINESSADMINISTRATOR_GROUP_ID = "BusinessAdministratorGroupId";
     private static final String EXCLUDED_OWNER_ID = "ExcludedOwnerId";
 
-    @Override
-    public String getName() {
-        return "Human Task";
-    }
-
-    @Override
-    public WorkItemLifeCycle initialize() {
-        return new DefaultWorkItemLifeCycle(
-                TRANSITION_CREATED_ACTIVE,
-                TRANSITION_ACTIVATED_CLAIM,
-                TRANSITION_ACTIVATED_ABORT,
-                TRANSITION_ACTIVATED_COMPLETE,
-                TRANSITION_RESERVED_RELEASE,
-                TRANSITION_RESERVED_ABORT,
-                TRANSITION_RESERVED_COMPLETE,
-                TRANSITION_ACTIVATED_COMPLETED);
-    }
-
-    @Override
-    public WorkItemTransition startingTransition(Map<String, Object> data, Policy... policies) {
-        return workItemLifeCycle.newTransition("activate", null, data, policies);
-    }
-
-    @Override
-    public WorkItemTransition abortTransition(String phaseStatus, Policy... policies) {
-        return workItemLifeCycle.newTransition("abort", phaseStatus, emptyMap(), policies);
+    public UserTaskKogitoWorkItemHandler() {
+        super();
     }
 
-    @Override
-    public WorkItemTransition completeTransition(String phaseStatus, Map<String, Object> data, Policy... policies) {
-        return workItemLifeCycle.newTransition("complete", phaseStatus, data, policies);
+    public UserTaskKogitoWorkItemHandler(Application application) {
+        super();
+        this.setApplication(application);
     }
 
-    static public Optional<WorkItemTransition> userTaskActivateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+    public Optional<WorkItemTransition> activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
         UserTasks userTasks = handler.getApplication().get(UserTasks.class);
 
         Object priority = workItem.getParameter(PRIORITY);
@@ -130,11 +79,11 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
         UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
 
         DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTask.createInstance();
-        instance.setId(workItem.getStringId());
         instance.setTaskName((String) workItem.getParameter(TASK_NAME));
         instance.setTaskDescription((String) workItem.getParameter(DESCRIPTION));
         instance.setTaskPriority(priorityInteger);
         instance.setExternalReferenceId(workItem.getStringId());
+
         instance.setMetadata("ProcessId", workItem.getProcessInstance().getProcessId());
         instance.setMetadata("ProcessType", workItem.getProcessInstance().getProcess().getType());
         instance.setMetadata("ProcessVersion", workItem.getProcessInstance().getProcessVersion());
@@ -144,114 +93,61 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler
         instance.setMetadata("RootProcessInstanceId", workItem.getProcessInstance().getRootProcessInstanceId());
         instance.setMetadata("ParentProcessInstanceId", workItem.getProcessInstance().getParentProcessInstanceId());
 
-        ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setPotentialUsers);
-        ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setPotentialGroups);
-        ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setAdminUsers);
-        ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setAdminGroups);
-        ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setExcludedUsers);
+        userTask.instances().create(instance);
+        instance.fireInitialStateChange();
+        workItem.getParameters().forEach(instance::setInput);
+
+        ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialUsers);
+        ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialGroups);
+        ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminUsers);
+        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.assign();
-        instance.transition(instance.createTransitionToken("activate", emptyMap()));
+        instance.transition(DefaultUserTaskLifeCycle.ACTIVATE, emptyMap(), IdentityProviders.of(WORKFLOW_ENGINE_USER));
 
         if (workItem instanceof InternalKogitoWorkItem ikw) {
             ikw.setExternalReferenceId(instance.getId());
             ikw.setActualOwner(instance.getActualOwner());
         }
-        userTask.instances().create(instance);
-        return Optional.empty();
-    }
 
-    static protected Set<String> toSet(String value) {
-        if (value == null) {
-            return null;
-        }
-        return Set.of(value.split(UT_SEPARATOR));
+        return Optional.empty();
     }
 
-    static public Optional<WorkItemTransition> userTaskClaimWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
-        workItem.removeOutput("ACTUAL_OWNER");
-
+    @Override
+    public Optional<WorkItemTransition> completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
         UserTasks userTasks = handler.getApplication().get(UserTasks.class);
         UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
         userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
-            Map<String, Object> data = transition.data();
             if (workItem instanceof InternalKogitoWorkItem ikw) {
-                getUserFromTransition(transition).ifPresent(ikw::setActualOwner);
-                if (data.containsKey("ACTUAL_OWNER") && ikw.getActualOwner() == null) {
-                    ut.setActuaOwner((String) data.get("ACTUAL_OWNER"));
-                }
-                if (ikw.getActualOwner() == null) {
-                    throw new InvalidTransitionException("transition claim does not contain user id");
-                }
+                ikw.setActualOwner(ut.getActualOwner());
             }
-            ut.setActuaOwner(workItem.getActualOwner());
-            ut.transition(ut.createTransitionToken("claim", emptyMap()));
-            userTask.instances().update(ut);
+            ut.transition(DefaultUserTaskLifeCycle.SKIP, Collections.singletonMap(PARAMETER_NOTIFY, Boolean.FALSE), IdentityProviders.of(WORKFLOW_ENGINE_USER));
         });
         return Optional.empty();
     }
 
-    static public Optional<WorkItemTransition> userTaskReleaseWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
-        if (workItem instanceof InternalKogitoWorkItem ikw) {
-            ikw.setActualOwner(null);
-        }
-        UserTasks userTasks = handler.getApplication().get(UserTasks.class);
-        UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
-        userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
-            ut.setActuaOwner(null);
-            ut.transition(ut.createTransitionToken("release", emptyMap()));
-            userTask.instances().update(ut);
-        });
-
-        return Optional.empty();
-    }
-
-    static public Optional<WorkItemTransition> userTaskCompleteWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+    @Override
+    public Optional<WorkItemTransition> abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
         UserTasks userTasks = handler.getApplication().get(UserTasks.class);
         UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
         userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
-            ut.transition(ut.createTransitionToken("complete", emptyMap()));
-            userTask.instances().update(ut);
+            if (workItem instanceof InternalKogitoWorkItem ikw) {
+                ikw.setActualOwner(ut.getActualOwner());
+            }
+            ut.transition(DefaultUserTaskLifeCycle.FAIL, Collections.singletonMap(PARAMETER_NOTIFY, Boolean.FALSE), IdentityProviders.of(WORKFLOW_ENGINE_USER));
         });
-        if (workItem instanceof InternalKogitoWorkItem ikw && ikw.getActualOwner() == null) {
-            getUserFromTransition(transition).ifPresent(user -> {
-                ikw.setActualOwner(user);
-            });
-        }
         return Optional.empty();
     }
 
-    static public Optional<WorkItemTransition> userTaskCompleteFromActiveWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem,
-            WorkItemTransition transition) {
-        UserTasks userTasks = handler.getApplication().get(UserTasks.class);
-        UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
-        userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
-            ut.transition(ut.createTransitionToken("complete", emptyMap()));
-            userTask.instances().update(ut);
-        });
-        if (workItem instanceof InternalKogitoWorkItem ikw) {
-            getUserFromTransition(transition).ifPresent(user -> {
-                ikw.setActualOwner(user);
-            });
+    protected Set<String> toSet(String value) {
+        if (value == null) {
+            return null;
         }
-        return Optional.empty();
-    }
-
-    static public Optional<WorkItemTransition> userTaskAbortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
-        UserTasks userTasks = handler.getApplication().get(UserTasks.class);
-        UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
-        userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
-            ut.transition(ut.createTransitionToken("skip", emptyMap()));
-            userTask.instances().update(ut);
-        });
-        return Optional.empty();
+        return Set.of(value.split(UT_SEPARATOR));
     }
 
-    private static Optional<String> getUserFromTransition(WorkItemTransition transition) {
-        Optional<SecurityPolicy> securityPolicy = transition.policies().stream().filter(SecurityPolicy.class::isInstance).map(SecurityPolicy.class::cast).findAny();
-        if (securityPolicy.isPresent()) {
-            return Optional.ofNullable(securityPolicy.get().getUser());
-        }
-        return Optional.empty();
+    @Override
+    public String getName() {
+        return "Human Task";
     }
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java
new file mode 100644
index 0000000000..7d0bc70557
--- /dev/null
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jbpm.usertask.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.usertask.UserTaskEventListener;
+import org.kie.kogito.usertask.events.UserTaskStateEvent;
+import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserTaskKogitoWorkItemHandlerProcessListener implements UserTaskEventListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserTaskKogitoWorkItemHandlerProcessListener.class);
+
+    private Processes processes;
+
+    public UserTaskKogitoWorkItemHandlerProcessListener(Processes processes) {
+        this.processes = processes;
+    }
+
+    @Override
+    public void onUserTaskState(UserTaskStateEvent event) {
+        UserTaskState userTaskState = event.getNewStatus();
+        if (!userTaskState.isTerminate()) {
+            return;
+        }
+
+        // we check first that the work item is not finished to convey the signal
+        Boolean notify = (Boolean) event.getUserTaskInstance().getMetadata().get(DefaultUserTaskLifeCycle.PARAMETER_NOTIFY);
+        if (notify != null && !notify) {
+            return;
+        }
+
+        LOG.debug("onUserTaskState {} on complete work item", event);
+        String processId = (String) event.getUserTaskInstance().getMetadata().get("ProcessId");
+        String processInstanceId = (String) event.getUserTaskInstance().getMetadata().get("ProcessInstanceId");
+
+        processes.processById(processId).instances().findById(processInstanceId).ifPresent(pi -> {
+            Map<String, Object> data = new HashMap<>(event.getUserTaskInstance().getOutputs());
+            data.put("ActorId", event.getUserTaskInstance().getActualOwner());
+            pi.completeWorkItem(event.getUserTaskInstance().getExternalReferenceId(), data);
+        });
+
+    }
+}
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
similarity index 74%
copy from jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java
copy to jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
index 92e2fa9c74..19a7d8c1e7 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
@@ -23,23 +23,16 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.kie.kogito.Application;
-import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
 import org.kie.kogito.usertask.UserTask;
-import org.kie.kogito.usertask.UserTaskConfig;
 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.model.DeadlineInfo;
 import org.kie.kogito.usertask.model.Reassignment;
 
-public class DefaultUserTask implements UserTask {
-
+public abstract class AbstractUserTask implements UserTask {
     private String separator = System.getProperty("org.jbpm.ht.user.separator", ",");
 
-    private Application application;
     private String id;
-    private UserTaskInstances userTaskInstances;
     private String name;
     private String taskName;
     private String taskDescription;
@@ -56,25 +49,9 @@ public class DefaultUserTask implements UserTask {
     private Collection<DeadlineInfo<Reassignment>> startReassigments;
     private Collection<DeadlineInfo<Reassignment>> endReassigments;
 
-    public DefaultUserTask() {
-        // nothing
-    }
-
-    public DefaultUserTask(String id, String name) {
-        this(null, id, name, new InMemoryUserTaskInstances());
-    }
-
-    public DefaultUserTask(Application application, String id, String name) {
-        this(application, id, name, new InMemoryUserTaskInstances());
-    }
-
-    public DefaultUserTask(Application application, String id, String name, UserTaskInstances userTaskInstances) {
-        this.application = application;
+    public AbstractUserTask(String id, String name) {
         this.id = id;
         this.name = name;
-        this.userTaskInstances = userTaskInstances;
-        this.userTaskInstances.setReconnectUserTaskInstance(this::connect);
-        this.userTaskInstances.setDisconnectUserTaskInstance(this::disconnect);
         this.skippable = Boolean.FALSE;
         this.potentialUsers = new HashSet<>();
         this.potentialGroups = new HashSet<>();
@@ -85,16 +62,12 @@ public class DefaultUserTask implements UserTask {
         this.endDeadlines = new HashSet<>();
         this.startReassigments = new HashSet<>();
         this.endReassigments = new HashSet<>();
-
-    }
-
-    public void setApplication(Application application) {
-        this.application = application;
     }
 
     @Override
     public UserTaskInstance createInstance() {
         DefaultUserTaskInstance instance = new DefaultUserTaskInstance(this);
+        instance.setUserTaskId(id());
         instance.setTaskName(getTaskName());
         instance.setTaskDescription(getTaskDescription());
         instance.setTaskPriority(getTaskPriority());
@@ -103,37 +76,9 @@ public class DefaultUserTask implements UserTask {
         instance.setAdminUsers(getAdminUsers());
         instance.setPotentialGroups(getPotentialGroups());
         instance.setExcludedUsers(getExcludedUsers());
-        connect(instance);
         return instance;
     }
 
-    private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) {
-        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
-        instance.setUserTask(null);
-        instance.setUserTaskEventSupport(null);
-        instance.setUserTaskLifeCycle(null);
-        instance.setInstances(null);
-        return instance;
-    }
-
-    public UserTaskInstance connect(UserTaskInstance userTaskInstance) {
-        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
-        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
-        KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider());
-        userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener);
-        impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager()));
-        instance.setUserTask(this);
-        instance.setUserTaskEventSupport(impl);
-        instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
-        instance.setInstances(userTaskInstances);
-        return instance;
-    }
-
-    @Override
-    public UserTaskInstances instances() {
-        return userTaskInstances;
-    }
-
     @Override
     public String id() {
         return id;
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java
similarity index 53%
copy from api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java
copy to jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java
index eb8290884a..076a62c313 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java
@@ -16,22 +16,31 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.kie.kogito.usertask.impl;
 
-package org.kie.kogito.usertask.lifecycle;
-
-import java.util.Map;
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 
+import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
 import org.kie.kogito.usertask.UserTaskInstance;
 
-public interface UserTaskLifeCycle {
-
-    Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition);
-
-    UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map<String, Object> data);
-
-    UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> emptyMap);
-
-    UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> emptyMap);
+public class BasicUserTaskAssignmentStrategy implements UserTaskAssignmentStrategy {
+
+    @Override
+    public String getName() {
+        return DEFAULT_NAME;
+    }
+
+    @Override
+    public Optional<String> computeAssigment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) {
+        Set<String> users = new HashSet<>(userTaskInstance.getPotentialUsers());
+        users.removeAll(userTaskInstance.getExcludedUsers());
+        if (users.size() == 1) {
+            return Optional.of(users.iterator().next());
+        }
+        return Optional.empty();
+    }
 
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java
index 92e2fa9c74..9b80e6e717 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java
@@ -18,115 +18,30 @@
  */
 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.Application;
-import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
-import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
 import org.kie.kogito.usertask.UserTaskConfig;
-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.model.DeadlineInfo;
-import org.kie.kogito.usertask.model.Reassignment;
-
-public class DefaultUserTask implements UserTask {
 
-    private String separator = System.getProperty("org.jbpm.ht.user.separator", ",");
+public class DefaultUserTask extends AbstractUserTask {
 
     private Application application;
-    private String id;
     private UserTaskInstances userTaskInstances;
-    private String name;
-    private String taskName;
-    private String taskDescription;
-    private String referenceName;
-    private Integer taskPriority;
-    private Boolean skippable;
-    private Set<String> potentialUsers;
-    private Set<String> potentialGroups;
-    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<Reassignment>> startReassigments;
-    private Collection<DeadlineInfo<Reassignment>> endReassigments;
-
-    public DefaultUserTask() {
-        // nothing
-    }
 
     public DefaultUserTask(String id, String name) {
-        this(null, id, name, new InMemoryUserTaskInstances());
+        super(id, name);
     }
 
     public DefaultUserTask(Application application, String id, String name) {
-        this(application, id, name, new InMemoryUserTaskInstances());
-    }
-
-    public DefaultUserTask(Application application, String id, String name, UserTaskInstances userTaskInstances) {
-        this.application = application;
-        this.id = id;
-        this.name = name;
-        this.userTaskInstances = userTaskInstances;
-        this.userTaskInstances.setReconnectUserTaskInstance(this::connect);
-        this.userTaskInstances.setDisconnectUserTaskInstance(this::disconnect);
-        this.skippable = Boolean.FALSE;
-        this.potentialUsers = new HashSet<>();
-        this.potentialGroups = new HashSet<>();
-        this.adminUsers = new HashSet<>();
-        this.adminGroups = new HashSet<>();
-        this.excludedUsers = new HashSet<>();
-        this.startDeadlines = new HashSet<>();
-        this.endDeadlines = new HashSet<>();
-        this.startReassigments = new HashSet<>();
-        this.endReassigments = new HashSet<>();
-
-    }
-
-    public void setApplication(Application application) {
+        super(id, name);
         this.application = application;
+        this.userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances();
     }
 
     @Override
-    public UserTaskInstance createInstance() {
-        DefaultUserTaskInstance instance = new DefaultUserTaskInstance(this);
-        instance.setTaskName(getTaskName());
-        instance.setTaskDescription(getTaskDescription());
-        instance.setTaskPriority(getTaskPriority());
-        instance.setPotentialUsers(getPotentialUsers());
-        instance.setPotentialGroups(getPotentialGroups());
-        instance.setAdminUsers(getAdminUsers());
-        instance.setPotentialGroups(getPotentialGroups());
-        instance.setExcludedUsers(getExcludedUsers());
-        connect(instance);
-        return instance;
-    }
-
-    private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) {
-        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
-        instance.setUserTask(null);
-        instance.setUserTaskEventSupport(null);
-        instance.setUserTaskLifeCycle(null);
-        instance.setInstances(null);
-        return instance;
-    }
-
-    public UserTaskInstance connect(UserTaskInstance userTaskInstance) {
-        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+    public UserTaskAssignmentStrategy getAssignmentStrategy() {
         UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
-        KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider());
-        userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener);
-        impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager()));
-        instance.setUserTask(this);
-        instance.setUserTaskEventSupport(impl);
-        instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
-        instance.setInstances(userTaskInstances);
-        return instance;
+        return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy();
     }
 
     @Override
@@ -134,170 +49,4 @@ public class DefaultUserTask implements UserTask {
         return userTaskInstances;
     }
 
-    @Override
-    public String id() {
-        return id;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    public void setSkippable(String skippable) {
-        this.skippable = Boolean.parseBoolean(skippable);
-    }
-
-    public Boolean getSkippable() {
-        return skippable;
-    }
-
-    @Override
-    public String getTaskName() {
-        return taskName;
-    }
-
-    public void setTaskName(String taskName) {
-        this.taskName = taskName;
-    }
-
-    @Override
-    public String getTaskDescription() {
-        return taskDescription;
-    }
-
-    public void setTaskDescription(String taskDescription) {
-        this.taskDescription = taskDescription;
-    }
-
-    @Override
-    public Integer getTaskPriority() {
-        return this.taskPriority;
-    }
-
-    public void setTaskPriority(Integer taskPriority) {
-        this.taskPriority = taskPriority;
-    }
-
-    public void setReferenceName(String referenceName) {
-        this.referenceName = referenceName;
-    }
-
-    @Override
-    public String getReferenceName() {
-        return referenceName;
-    }
-
-    @Override
-    public Set<String> getPotentialUsers() {
-        return this.potentialUsers;
-    }
-
-    public void setPotentialUsers(String potentialUsers) {
-        this.setPotentialUsers(toSet(potentialUsers));
-    }
-
-    public void setPotentialUsers(Set<String> potentialUsers) {
-        this.potentialUsers = potentialUsers;
-    }
-
-    @Override
-    public Set<String> getPotentialGroups() {
-        return this.potentialGroups;
-    }
-
-    public void setPotentialGroups(String potentialGroups) {
-        this.setPotentialGroups(toSet(potentialGroups));
-    }
-
-    public void setPotentialGroups(Set<String> potentialGroups) {
-        this.potentialGroups = potentialGroups;
-    }
-
-    @Override
-    public Set<String> getAdminUsers() {
-        return this.adminUsers;
-    }
-
-    public void setAdminUsers(String adminUsers) {
-        this.setAdminUsers(toSet(adminUsers));
-    }
-
-    public void setAdminUsers(Set<String> adminUsers) {
-        this.adminUsers = adminUsers;
-    }
-
-    @Override
-    public Set<String> getAdminGroups() {
-        return this.adminGroups;
-    }
-
-    public void setAdminGroups(String adminGroups) {
-        this.setAdminGroups(toSet(adminGroups));
-    }
-
-    public void setAdminGroups(Set<String> adminGroups) {
-        this.adminGroups = adminGroups;
-    }
-
-    @Override
-    public Set<String> getExcludedUsers() {
-        return this.excludedUsers;
-    }
-
-    public void setExcludedUsers(String excludedUsers) {
-        this.setExcludedUsers(toSet(excludedUsers));
-    }
-
-    public void setExcludedUsers(Set<String> excludedUsers) {
-        this.excludedUsers = excludedUsers;
-    }
-
-    @Override
-    public Collection<DeadlineInfo<Map<String, Object>>> getNotStartedDeadlines() {
-        return startDeadlines;
-    }
-
-    public void setNotStartedDeadLines(String deadlines) {
-        this.startDeadlines = DeadlineHelper.parseDeadlines(deadlines);
-    }
-
-    @Override
-    public Collection<DeadlineInfo<Map<String, Object>>> getNotCompletedDeadlines() {
-        return endDeadlines;
-    }
-
-    public void setNotCompletedDeadlines(String notStarted) {
-        this.endDeadlines = DeadlineHelper.parseDeadlines(notStarted);
-    }
-
-    @Override
-    public Collection<DeadlineInfo<Reassignment>> getNotStartedReassignments() {
-        return startReassigments;
-    }
-
-    public void setNotStartedReassignments(String reassignments) {
-        this.startReassigments = DeadlineHelper.parseReassignments(reassignments);
-    }
-
-    @Override
-    public Collection<DeadlineInfo<Reassignment>> getNotCompletedReassigments() {
-        return endReassigments;
-    }
-
-    public void setNotCompletedReassigments(String reassignments) {
-        this.endReassigments = DeadlineHelper.parseReassignments(reassignments);
-    }
-
-    protected Set<String> toSet(String value) {
-        if (value == null) {
-            return new HashSet<>();
-        }
-        Set<String> store = new HashSet<>();
-        for (String item : value.split(separator)) {
-            store.add(item);
-        }
-        return store;
-    }
-
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java
new file mode 100644
index 0000000000..5c6029b477
--- /dev/null
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java
@@ -0,0 +1,54 @@
+/*
+ * 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.usertask.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
+
+public class DefaultUserTaskAssignmentStrategyConfig implements UserTaskAssignmentStrategyConfig {
+
+    private Map<String, UserTaskAssignmentStrategy> userTaskAssignmentStrategies;
+
+    public DefaultUserTaskAssignmentStrategyConfig() {
+        this.userTaskAssignmentStrategies = new HashMap<>();
+        BasicUserTaskAssignmentStrategy strategy = new BasicUserTaskAssignmentStrategy();
+        this.userTaskAssignmentStrategies.put(strategy.getName(), strategy);
+    }
+
+    public DefaultUserTaskAssignmentStrategyConfig(Iterable<UserTaskAssignmentStrategy> userTaskAssignmentStrategies) {
+        this();
+        userTaskAssignmentStrategies.forEach(uts -> this.userTaskAssignmentStrategies.put(uts.getName(), uts));
+    }
+
+    @Override
+    public List<UserTaskAssignmentStrategy> userTaskAssignmentStrategies() {
+        return new ArrayList<>(userTaskAssignmentStrategies.values());
+    }
+
+    @Override
+    public UserTaskAssignmentStrategy forName(String name) {
+        return userTaskAssignmentStrategies.get(name);
+    }
+
+}
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 93d012f011..7282ce7253 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
@@ -27,8 +27,10 @@ import org.kie.kogito.services.identity.NoOpIdentityProvider;
 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.UserTaskAssignmentStrategyConfig;
 import org.kie.kogito.usertask.UserTaskConfig;
 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;
 
@@ -39,13 +41,17 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
     private JobsService jobService;
     private IdentityProvider identityProvider;
     private UserTaskLifeCycle userTaskLifeCycle;
+    private UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig;
+    private UserTaskInstances userTaskInstances;
 
     public DefaultUserTaskConfig() {
         this(new DefaultUserTaskEventListenerConfig(),
                 new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()),
                 null,
                 new NoOpIdentityProvider(),
-                new DefaultUserTaskLifeCycle());
+                new DefaultUserTaskLifeCycle(),
+                new DefaultUserTaskAssignmentStrategyConfig(),
+                new InMemoryUserTaskInstances());
     }
 
     public DefaultUserTaskConfig(
@@ -53,13 +59,17 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
             Iterable<UnitOfWorkManager> unitOfWorkManager,
             Iterable<JobsService> jobService,
             Iterable<IdentityProvider> identityProvider,
-            Iterable<UserTaskLifeCycle> userTaskLifeCycle) {
+            Iterable<UserTaskLifeCycle> userTaskLifeCycle,
+            Iterable<UserTaskAssignmentStrategyConfig> userTaskAssignmentStrategyConfig,
+            Iterable<UserTaskInstances> userTaskInstances) {
 
         this.userTaskEventListeners = singleton(userTaskEventListenerConfig, DefaultUserTaskEventListenerConfig::new);
         this.unitOfWorkManager = singleton(unitOfWorkManager, () -> new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()));
         this.jobService = singleton(jobService, () -> null);
         this.identityProvider = singleton(identityProvider, NoOpIdentityProvider::new);
         this.userTaskLifeCycle = singleton(userTaskLifeCycle, DefaultUserTaskLifeCycle::new);
+        this.userTaskAssignmentStrategyConfig = singleton(userTaskAssignmentStrategyConfig, DefaultUserTaskAssignmentStrategyConfig::new);
+        this.userTaskInstances = singleton(userTaskInstances, InMemoryUserTaskInstances::new);
 
     }
 
@@ -76,12 +86,16 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
             UnitOfWorkManager unitOfWorkManager,
             JobsService jobService,
             IdentityProvider identityProvider,
-            UserTaskLifeCycle userTaskLifeCycle) {
+            UserTaskLifeCycle userTaskLifeCycle,
+            DefaultUserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig,
+            UserTaskInstances userTaskInstances) {
         this.userTaskEventListeners = userTaskEventListenerConfig;
         this.unitOfWorkManager = unitOfWorkManager;
         this.jobService = jobService;
         this.identityProvider = identityProvider;
         this.userTaskLifeCycle = userTaskLifeCycle;
+        this.userTaskAssignmentStrategyConfig = userTaskAssignmentStrategyConfig;
+        this.userTaskInstances = userTaskInstances;
     }
 
     @Override
@@ -109,4 +123,14 @@ public class DefaultUserTaskConfig implements UserTaskConfig {
         return userTaskLifeCycle;
     }
 
+    @Override
+    public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategies() {
+        return userTaskAssignmentStrategyConfig;
+    }
+
+    @Override
+    public UserTaskInstances userTaskInstances() {
+        return userTaskInstances;
+    }
+
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java
index 9cb7f3ac09..086c729124 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java
@@ -32,8 +32,13 @@ public class DefaultUserTaskEventListenerConfig implements UserTaskEventListener
         this.listeners = new ArrayList<>();
     }
 
+    public DefaultUserTaskEventListenerConfig(Iterable<UserTaskEventListener> listeners) {
+        this();
+        listeners.forEach(this::addUserTaskEventListener);
+    }
+
     public DefaultUserTaskEventListenerConfig(List<UserTaskEventListener> listeners) {
-        this.listeners = new ArrayList<>(listeners);
+        this((Iterable<UserTaskEventListener>) listeners);
     }
 
     public void addUserTaskEventListener(UserTaskEventListener userTaskEventListener) {
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 8adb9f07d0..e5265ae3f7 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
@@ -19,7 +19,7 @@
 package org.kie.kogito.usertask.impl;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -28,6 +28,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 
+import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport;
 import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport.AssignmentType;
 import org.kie.kogito.usertask.UserTask;
@@ -35,7 +36,6 @@ import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.UserTaskInstances;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
 import org.kie.kogito.usertask.lifecycle.UserTaskState;
-import org.kie.kogito.usertask.lifecycle.UserTaskTransition;
 import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken;
 import org.kie.kogito.usertask.model.Attachment;
 import org.kie.kogito.usertask.model.Comment;
@@ -46,6 +46,8 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
 
     private String id;
 
+    private String userTaskId;
+
     private UserTaskState status;
     private String actualOwner;
     private String taskName;
@@ -75,6 +77,9 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     private UserTaskLifeCycle setUserTaskLifeCycle;
 
     public DefaultUserTaskInstance() {
+        this.inputs = new HashMap<>();
+        this.outputs = new HashMap<>();
+        this.status = UserTaskState.initalized();
         this.metadata = new HashMap<>();
         this.attachments = new ArrayList<>();
         this.comments = new ArrayList<>();
@@ -86,27 +91,10 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     }
 
     public DefaultUserTaskInstance(UserTask userTask) {
+        this();
         this.id = UUID.randomUUID().toString();
         this.userTask = userTask;
         this.instances = userTask.instances();
-        this.status = UserTaskState.initalized();
-        this.metadata = new HashMap<>();
-        this.attachments = new ArrayList<>();
-        this.comments = new ArrayList<>();
-        this.potentialUsers = new HashSet<>();
-        this.potentialGroups = new HashSet<>();
-        this.adminUsers = new HashSet<>();
-        this.adminGroups = new HashSet<>();
-        this.excludedUsers = new HashSet<>();
-    }
-
-    public void assign() {
-        Set<String> potentialUsers = new HashSet<>(this.getPotentialUsers());
-        potentialUsers.removeAll(getExcludedUsers());
-
-        if (potentialUsers.size() == 1) {
-            this.actualOwner = potentialUsers.iterator().next();
-        }
     }
 
     public void setUserTaskEventSupport(KogitoUserTaskEventSupport userTaskEventSupport) {
@@ -121,22 +109,17 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         this.instances = instances;
     }
 
-    @Override
-    public void complete() {
-        UserTaskTransitionToken transition = this.setUserTaskLifeCycle.newCompleteTransitionToken(this, Collections.emptyMap());
-        transition(transition);
-        instances.remove(id);
+    public void setId(String id) {
+        this.id = id;
     }
 
     @Override
-    public void abort() {
-        UserTaskTransitionToken transition = this.setUserTaskLifeCycle.newAbortTransitionToken(this, Collections.emptyMap());
-        transition(transition);
-        instances.remove(id);
+    public String getUserTaskId() {
+        return userTaskId;
     }
 
-    public void setId(String id) {
-        this.id = id;
+    public void setUserTaskId(String userTaskId) {
+        this.userTaskId = userTaskId;
     }
 
     @Override
@@ -162,8 +145,9 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     public void setActuaOwner(String actualOwner) {
         this.actualOwner = actualOwner;
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName());
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
         }
+        updatePersistence();
     }
 
     @Override
@@ -181,21 +165,29 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     }
 
     @Override
-    public UserTaskTransitionToken createTransitionToken(String transitionId, Map<String, Object> data) {
-        return this.setUserTaskLifeCycle.newTransitionToken(transitionId, this, data);
-    }
-
-    @Override
-    public void transition(UserTaskTransitionToken token) {
-        Optional<UserTaskTransitionToken> next = Optional.of(token);
+    public void transition(String transitionId, Map<String, Object> data, IdentityProvider identity) {
+        Optional<UserTaskTransitionToken> next = Optional.of(this.setUserTaskLifeCycle.newTransitionToken(transitionId, this, data));
         while (next.isPresent()) {
-            UserTaskTransition transition = next.get().transition();
-            next = this.setUserTaskLifeCycle.transition(this, token);
+            UserTaskTransitionToken transition = next.get();
+            next = this.setUserTaskLifeCycle.transition(this, transition, identity);
             this.status = transition.target();
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, transition.source().getName(), transition.target().getName());
-            if (this.status.isTerminate().isPresent()) {
-                this.instances.remove(this.id);
-            }
+            this.updatePersistenceOrRemove();
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, transition.source(), transition.target());
+        }
+
+    }
+
+    private void updatePersistence() {
+        if (this.instances != null) {
+            this.instances.update(this);
+        }
+    }
+
+    private void updatePersistenceOrRemove() {
+        if (this.status.isTerminate()) {
+            this.instances.remove(this.id);
+        } else {
+            this.instances.update(this);
         }
     }
 
@@ -204,7 +196,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         return userTask;
     }
 
-    public void setUserTask(DefaultUserTask userTask) {
+    public void setUserTask(UserTask userTask) {
         this.userTask = userTask;
     }
 
@@ -213,7 +205,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     }
 
     public void setInputs(Map<String, Object> inputs) {
-        this.inputs = inputs;
+        inputs.forEach(this::setInput);
     }
 
     public Map<String, Object> getOutputs() {
@@ -221,7 +213,25 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     }
 
     public void setOutputs(Map<String, Object> outputs) {
-        this.outputs = outputs;
+        outputs.forEach(this::setOutput);
+    }
+
+    @Override
+    public void setInput(String key, Object newValue) {
+        Object oldValue = this.inputs.put(key, newValue);
+        if (this.userTaskEventSupport != null) {
+            this.userTaskEventSupport.fireOnUserTaskInputVariableChange(this, key, oldValue, newValue);
+        }
+        updatePersistence();
+    }
+
+    @Override
+    public void setOutput(String key, Object newValue) {
+        Object oldValue = this.outputs.put(key, newValue);
+        if (this.userTaskEventSupport != null) {
+            this.userTaskEventSupport.fireOnUserTaskOutputVariableChange(this, key, oldValue, newValue);
+        }
+        updatePersistence();
     }
 
     /**
@@ -237,7 +247,14 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     public void setTaskName(String taskName) {
         this.taskName = taskName;
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName());
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
+        }
+        updatePersistence();
+    }
+
+    public void fireInitialStateChange() {
+        if (this.userTaskEventSupport != null) {
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
         }
     }
 
@@ -254,8 +271,9 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     public void setTaskDescription(String taskDescription) {
         this.taskDescription = taskDescription;
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName());
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
         }
+        updatePersistence();
     }
 
     /**
@@ -271,8 +289,9 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
     public void setTaskPriority(Integer taskPriority) {
         this.taskPriority = taskPriority;
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName());
+            this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
         }
+        updatePersistence();
     }
 
     /**
@@ -291,6 +310,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USER_OWNERS, oldValues, potentialUsers);
         }
+        updatePersistence();
     }
 
     /**
@@ -309,6 +329,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USER_GROUPS, oldValues, potentialGroups);
         }
+        updatePersistence();
     }
 
     /**
@@ -327,6 +348,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.ADMIN_USERS, oldValues, adminUsers);
         }
+        updatePersistence();
     }
 
     /**
@@ -345,6 +367,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.ADMIN_GROUPS, oldValues, adminGroups);
         }
+        updatePersistence();
     }
 
     /**
@@ -363,6 +386,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USERS_EXCLUDED, oldValues, excludedUsers);
         }
+        updatePersistence();
     }
 
     /**
@@ -370,41 +394,64 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
      * 
      * @return A map which key is the attachment id and value the attachment object
      */
+
     public List<Attachment> getAttachments() {
         return attachments;
     }
 
     @Override
-    public void addAttachment(Attachment attachment) {
+    public Attachment addAttachment(Attachment attachment) {
+        attachment.setId(UUID.randomUUID().toString());
+        attachment.setUpdatedAt(new Date());
         this.attachments.add(attachment);
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAttachmentAdded(this, attachment);
         }
+        updatePersistence();
+        return attachment;
     }
 
     @Override
-    public void updateAttachment(Attachment newAttachment) {
+    public Attachment updateAttachment(Attachment newAttachment) {
         Optional<Attachment> oldAttachment = this.attachments.stream().filter(e -> e.getId().equals(newAttachment.getId())).findFirst();
         if (oldAttachment.isEmpty()) {
-            return;
+            return null;
         }
         this.attachments.remove(oldAttachment.get());
+        if (newAttachment.getName() == null) {
+            String path = newAttachment.getContent().getPath();
+            int idx = path.lastIndexOf("/");
+            if (idx > 0) {
+                path = path.substring(idx + 1);
+            }
+            newAttachment.setName(path);
+        }
+        newAttachment.setUpdatedAt(new Date());
         this.attachments.add(newAttachment);
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskAttachmentChange(this, oldAttachment.get(), newAttachment);
         }
+        updatePersistence();
+        return newAttachment;
     }
 
     @Override
-    public void removeAttachment(Attachment oldAttachment) {
-        this.attachments.remove(oldAttachment);
+    public Attachment removeAttachment(Attachment attachment) {
+        Optional<Attachment> oldAttachment = this.attachments.stream().filter(e -> e.getId().equals(attachment.getId())).findFirst();
+        if (oldAttachment.isEmpty()) {
+            return null;
+        }
+        this.attachments.remove(attachment);
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOnUserTaskAttachmentDeleted(this, oldAttachment);
+            this.userTaskEventSupport.fireOnUserTaskAttachmentDeleted(this, oldAttachment.get());
         }
+        updatePersistence();
+        return oldAttachment.get();
     }
 
     public void setAttachments(List<Attachment> attachments) {
         this.attachments = attachments;
+        updatePersistence();
 
     }
 
@@ -413,45 +460,61 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
      * 
      * @return A map which key is the comment id and value the comment object
      */
+
     public List<Comment> getComments() {
         return comments;
     }
 
     @Override
-    public void addComment(Comment comment) {
+    public Comment addComment(Comment comment) {
+        comment.setId(UUID.randomUUID().toString());
+        comment.setUpdatedAt(new Date());
         this.comments.add(comment);
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskCommentAdded(this, comment);
         }
+        updatePersistence();
+        return comment;
     }
 
     @Override
-    public void updateComment(Comment newComment) {
+    public Comment updateComment(Comment newComment) {
         Optional<Comment> oldComment = this.comments.stream().filter(e -> e.getId().equals(newComment.getId())).findFirst();
         if (oldComment.isEmpty()) {
-            return;
+            return null;
         }
         this.comments.remove(oldComment.get());
+        newComment.setUpdatedAt(new Date());
         this.comments.add(newComment);
         if (this.userTaskEventSupport != null) {
             this.userTaskEventSupport.fireOnUserTaskCommentChange(this, oldComment.get(), newComment);
         }
+        updatePersistence();
+        return newComment;
     }
 
     @Override
-    public void removeComment(Comment comment) {
+    public Comment removeComment(Comment comment) {
+        Optional<Comment> oldComment = this.comments.stream().filter(e -> e.getId().equals(comment.getId())).findFirst();
+        if (oldComment.isEmpty()) {
+            return null;
+        }
         this.comments.remove(comment);
         if (this.userTaskEventSupport != null) {
-            this.userTaskEventSupport.fireOnUserTaskCommentDeleted(this, comment);
+            this.userTaskEventSupport.fireOnUserTaskCommentDeleted(this, oldComment.get());
         }
+        updatePersistence();
+        return oldComment.get();
     }
 
     public void setComments(List<Comment> comments) {
         this.comments = comments;
+        updatePersistence();
     }
 
     public void setMetadata(String key, Object value) {
         this.metadata.put(key, value);
+        updatePersistence();
     }
 
     public Map<String, Object> getMetadata() {
@@ -460,6 +523,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
 
     public void setMetadata(Map<String, Object> metadata) {
         this.metadata = metadata;
+        updatePersistence();
     }
 
     @Override
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 a2a664e82d..e291867332 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
@@ -25,13 +25,18 @@ import java.util.List;
 import java.util.Map;
 
 import org.kie.kogito.Application;
+import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
 import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTaskInstances;
 import org.kie.kogito.usertask.UserTasks;
 
 public class DefaultUserTasks implements UserTasks {
 
     private Map<String, UserTask> userTasks;
     private Application application;
+    private UserTaskInstances userTaskInstances;
 
     public DefaultUserTasks() {
         this.userTasks = new HashMap<>();
@@ -49,6 +54,9 @@ public class DefaultUserTasks implements UserTasks {
             UserTask userTask = userTaskIterator.next();
             this.userTasks.put(userTask.id(), userTask);
         }
+        userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances();
+        userTaskInstances.setDisconnectUserTaskInstance(this::disconnect);
+        userTaskInstances.setReconnectUserTaskInstance(this::connect);
     }
 
     @Override
@@ -61,4 +69,30 @@ public class DefaultUserTasks implements UserTasks {
         return userTasks.keySet();
     }
 
+    @Override
+    public UserTaskInstances instances() {
+        return userTaskInstances;
+    }
+
+    public UserTaskInstance disconnect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        instance.setUserTask(null);
+        instance.setUserTaskEventSupport(null);
+        instance.setUserTaskLifeCycle(null);
+        instance.setInstances(null);
+        return instance;
+    }
+
+    public UserTaskInstance connect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider());
+        userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener);
+        impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager()));
+        instance.setUserTask(application.get(UserTasks.class).userTaskById(instance.getUserTaskId()));
+        instance.setUserTaskEventSupport(impl);
+        instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
+        instance.setInstances(userTaskInstances);
+        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 6704fbd83a..def2088da3 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
@@ -18,28 +18,42 @@
  */
 package org.kie.kogito.usertask.impl;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 
+import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.UserTaskInstances;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
 public class InMemoryUserTaskInstances implements UserTaskInstances {
 
+    private static Logger LOG = LoggerFactory.getLogger(InMemoryUserTaskInstances.class);
+
     private Map<String, byte[]> userTaskInstances;
     private Function<UserTaskInstance, UserTaskInstance> reconnectUserTaskInstance;
     private Function<UserTaskInstance, UserTaskInstance> disconnectUserTaskInstance;
     private ObjectMapper mapper;
 
     public InMemoryUserTaskInstances() {
+        LOG.info("Initializing InMemoryUsertaskInstances");
         this.userTaskInstances = new HashMap<>();
-        this.reconnectUserTaskInstance = Function.identity();
-        this.disconnectUserTaskInstance = Function.identity();
+        this.reconnectUserTaskInstance = null;
+        this.disconnectUserTaskInstance = null;
         this.mapper = new ObjectMapper();
+        this.mapper.registerModule(new JavaTimeModule());
     }
 
     @Override
@@ -55,13 +69,68 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
     @Override
     public Optional<UserTaskInstance> findById(String userTaskInstanceId) {
         try {
+            if (!userTaskInstances.containsKey(userTaskInstanceId)) {
+                return Optional.empty();
+            }
             UserTaskInstance userTaskInstance = mapper.readValue(userTaskInstances.get(userTaskInstanceId), DefaultUserTaskInstance.class);
             return Optional.ofNullable(reconnectUserTaskInstance.apply(userTaskInstance));
         } catch (Exception e) {
+            LOG.error("during find by Id {}", userTaskInstanceId, e);
             return Optional.empty();
         }
     }
 
+    @Override
+    public List<UserTaskInstance> findByIdentity(IdentityProvider identity) {
+        try {
+            String user = identity.getName();
+            Collection<String> roles = identity.getRoles();
+            List<UserTaskInstance> users = new ArrayList<>();
+            for (String id : userTaskInstances.keySet()) {
+                UserTaskInstance userTaskInstance = mapper.readValue(userTaskInstances.get(id), DefaultUserTaskInstance.class);
+                if (checkVisibility(userTaskInstance, user, roles)) {
+                    users.add(reconnectUserTaskInstance.apply(userTaskInstance));
+                }
+            }
+            return users;
+        } catch (Exception e) {
+            LOG.error("during find by Identity {}", identity.getName(), e);
+            return Collections.emptyList();
+        }
+    }
+
+    private boolean checkVisibility(UserTaskInstance userTaskInstance, String user, Collection<String> roles) {
+        Set<String> adminUsers = userTaskInstance.getAdminUsers();
+        if (adminUsers.contains(user)) {
+            return true;
+        }
+
+        Set<String> userAdminGroups = new HashSet<>(userTaskInstance.getAdminGroups());
+        userAdminGroups.retainAll(roles);
+        if (!userAdminGroups.isEmpty()) {
+            return true;
+        }
+
+        if (userTaskInstance.getActualOwner() != null && userTaskInstance.getActualOwner().equals(user)) {
+            return true;
+        }
+
+        // there is no user
+        Set<String> users = new HashSet<>(userTaskInstance.getPotentialUsers());
+        users.removeAll(userTaskInstance.getExcludedUsers());
+        if (users.contains(user)) {
+            return true;
+        }
+
+        Set<String> userPotGroups = new HashSet<>(userTaskInstance.getPotentialGroups());
+        userPotGroups.retainAll(roles);
+        if (!userPotGroups.isEmpty()) {
+            return true;
+        }
+
+        return false;
+    }
+
     @Override
     public boolean exists(String userTaskInstanceId) {
         return userTaskInstances.containsKey(userTaskInstanceId);
@@ -74,6 +143,7 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
             userTaskInstances.put(userTaskInstance.getId(), data);
             return reconnectUserTaskInstance.apply(userTaskInstance);
         } catch (Exception e) {
+            LOG.error("during create {}", userTaskInstance.getId(), e);
             return null;
         }
     }
@@ -85,6 +155,7 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
             userTaskInstances.put(userTaskInstance.getId(), data);
             return userTaskInstance;
         } catch (Exception e) {
+            LOG.error("during udpate {}", userTaskInstance.getId(), e);
             return null;
         }
     }
@@ -92,8 +163,12 @@ public class InMemoryUserTaskInstances implements UserTaskInstances {
     @Override
     public UserTaskInstance remove(String userTaskInstanceId) {
         try {
+            if (!userTaskInstances.containsKey(userTaskInstanceId)) {
+                return null;
+            }
             return disconnectUserTaskInstance.apply(mapper.readValue(userTaskInstances.remove(userTaskInstanceId), DefaultUserTaskInstance.class));
         } catch (Exception e) {
+            LOG.error("during remove {}", userTaskInstanceId, e);
             return null;
         }
     }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java
index 1a2c3be5d6..b337d159d4 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java
@@ -37,6 +37,7 @@ import org.kie.kogito.usertask.impl.events.UserTaskCommentEventImpl;
 import org.kie.kogito.usertask.impl.events.UserTaskDeadlineEventImpl;
 import org.kie.kogito.usertask.impl.events.UserTaskStateEventImpl;
 import org.kie.kogito.usertask.impl.events.UserTaskVariableEventImpl;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.model.Attachment;
 import org.kie.kogito.usertask.model.Comment;
 
@@ -84,7 +85,7 @@ public class KogitoUserTaskEventSupportImpl implements KogitoUserTaskEventSuppor
     @Override
     public void fireOneUserTaskStateChange(
             UserTaskInstance userTaskInstance,
-            String oldStatus, String newStatus) {
+            UserTaskState oldStatus, UserTaskState newStatus) {
         UserTaskStateEventImpl event = new UserTaskStateEventImpl(userTaskInstance, oldStatus, newStatus, identityProvider.getName());
         event.setOldStatus(oldStatus);
         event.setNewStatus(newStatus);
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
new file mode 100644
index 0000000000..da83176f6d
--- /dev/null
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java
@@ -0,0 +1,265 @@
+/*
+ * 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.usertask.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.kie.kogito.Application;
+import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTaskService;
+import org.kie.kogito.usertask.UserTasks;
+import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
+import org.kie.kogito.usertask.lifecycle.UserTaskTransition;
+import org.kie.kogito.usertask.model.Attachment;
+import org.kie.kogito.usertask.model.Comment;
+import org.kie.kogito.usertask.view.UserTaskTransitionView;
+import org.kie.kogito.usertask.view.UserTaskView;
+
+public class UserTaskServiceImpl implements UserTaskService {
+
+    private Application application;
+
+    public UserTaskServiceImpl(Application application) {
+        this.application = application;
+    }
+
+    @Override
+    public Optional<UserTaskView> getUserTaskInstance(String taskId, IdentityProvider identity) {
+        return application.get(UserTasks.class).instances().findById(taskId).map(this::toUserTaskView);
+    }
+
+    @Override
+    public List<UserTaskView> list(IdentityProvider identity) {
+        return application.get(UserTasks.class).instances().findByIdentity(identity).stream().map(this::toUserTaskView).toList();
+    }
+
+    private UserTaskView toUserTaskView(UserTaskInstance instance) {
+        UserTaskView view = new UserTaskView();
+        view.setId(instance.getId());
+        view.setUserTaskId(instance.getUserTaskId());
+        view.setStatus(instance.getStatus());
+        view.setTaskName(instance.getTaskName());
+        view.setTaskDescription(instance.getTaskDescription());
+        view.setTaskPriority(instance.getTaskPriority());
+        view.setPotentialUsers(instance.getPotentialUsers());
+        view.setPotentialGroups(instance.getPotentialGroups());
+        view.setExcludedUsers(instance.getExcludedUsers());
+        view.setAdminUsers(instance.getAdminUsers());
+        view.setAdminGroups(instance.getAdminGroups());
+        view.setActualOwner(instance.getActualOwner());
+        view.setInputs(instance.getInputs());
+        view.setOutputs(instance.getOutputs());
+        view.setMetadata(instance.getMetadata());
+        return view;
+    }
+
+    @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));
+        });
+    }
+
+    @Override
+    public List<UserTaskTransitionView> allowedTransitions(String taskId, IdentityProvider identity) {
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Collections.emptyList();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        UserTaskLifeCycle userTaskLifeCycle = application.config().get(UserTaskConfig.class).userTaskLifeCycle();
+        List<UserTaskTransition> transitions = userTaskLifeCycle.allowedTransitions(ut);
+        return toUserTaskTransitionView(transitions);
+    }
+
+    private List<UserTaskTransitionView> toUserTaskTransitionView(List<UserTaskTransition> transitions) {
+        List<UserTaskTransitionView> views = new ArrayList<>();
+        for (UserTaskTransition transition : transitions) {
+            UserTaskTransitionView view = new UserTaskTransitionView();
+            view.setTransitionId(transition.id());
+            view.setSource(transition.source());
+            view.setTarget(transition.target());
+            views.add(view);
+        }
+
+        return views;
+    }
+
+    @Override
+    public Optional<UserTaskView> setOutputs(String taskId, 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();
+            data.forEach(ut::setOutput);
+            return Optional.of(toUserTaskView(ut));
+        });
+    }
+
+    @Override
+    public Optional<UserTaskView> setInputs(String taskId, 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();
+            data.forEach(ut::setInput);
+            return Optional.of(toUserTaskView(ut));
+        });
+    }
+
+    @Override
+    public List<Comment> getComments(String taskId, IdentityProvider identity) {
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Collections.emptyList();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        return new ArrayList<>(ut.getComments());
+    }
+
+    @Override
+    public Optional<Comment> getComment(String taskId, String commentId, IdentityProvider identity) {
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Optional.empty();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        return Optional.ofNullable(ut.findCommentById(commentId));
+    }
+
+    @Override
+    public Optional<Comment> addComment(String taskId, Comment comment, 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();
+            Comment wrap = new Comment(null, identity.getName());
+            wrap.setContent(comment.getContent());
+            return Optional.ofNullable(ut.addComment(wrap));
+        });
+    }
+
+    @Override
+    public Optional<Comment> updateComment(String taskId, Comment comment, 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();
+            Comment wrap = new Comment(comment.getId(), identity.getName());
+            wrap.setContent(comment.getContent());
+            return Optional.ofNullable(ut.updateComment(wrap));
+        });
+    }
+
+    @Override
+    public Optional<Comment> removeComment(String taskId, String commentId, 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();
+            return Optional.ofNullable(ut.removeComment(new Comment(commentId, identity.getName())));
+        });
+    }
+
+    @Override
+    public List<Attachment> getAttachments(String taskId, IdentityProvider identity) {
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Collections.emptyList();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        return new ArrayList<>(ut.getAttachments());
+    }
+
+    @Override
+    public Optional<Attachment> getAttachment(String taskId, String attachmentId, IdentityProvider identity) {
+        Optional<UserTaskInstance> userTaskInstance = application.get(UserTasks.class).instances().findById(taskId);
+        if (userTaskInstance.isEmpty()) {
+            return Optional.empty();
+        }
+        UserTaskInstance ut = userTaskInstance.get();
+        return Optional.ofNullable(ut.findAttachmentById(attachmentId));
+    }
+
+    @Override
+    public Optional<Attachment> addAttachment(String taskId, Attachment attachment, 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();
+            Attachment wrap = new Attachment(null, identity.getName());
+            wrap.setContent(attachment.getContent());
+            wrap.setName(attachment.getName());
+            return Optional.ofNullable(ut.addAttachment(wrap));
+        });
+    }
+
+    @Override
+    public Optional<Attachment> updateAttachment(String taskId, Attachment attachment, 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();
+            Attachment wrap = new Attachment(attachment.getId(), identity.getName());
+            wrap.setContent(attachment.getContent());
+            wrap.setName(attachment.getName());
+            return Optional.ofNullable(ut.updateAttachment(attachment));
+        });
+    }
+
+    @Override
+    public Optional<Attachment> removeAttachment(String taskId, String attachmentId, 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();
+            return Optional.ofNullable(ut.removeAttachment(new Attachment(attachmentId, identity.getName())));
+        });
+    }
+
+}
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java
index 1bc9005e1f..e7f734ed08 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java
@@ -20,34 +20,37 @@ package org.kie.kogito.usertask.impl.events;
 
 import org.kie.kogito.usertask.UserTaskInstance;
 import org.kie.kogito.usertask.events.UserTaskStateEvent;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
 
 public class UserTaskStateEventImpl extends UserTaskEventImpl implements UserTaskStateEvent {
 
     private static final long serialVersionUID = 4556236095420836309L;
-    private String oldStatus;
-    private String newStatus;
+    private UserTaskState oldStatus;
+    private UserTaskState newStatus;
 
-    public UserTaskStateEventImpl(UserTaskInstance userTaskInstance, String oldStatus, String newStatus, String user) {
+    public UserTaskStateEventImpl(UserTaskInstance userTaskInstance, UserTaskState oldStatus, UserTaskState newStatus, String user) {
         super(userTaskInstance, user);
+        this.oldStatus = oldStatus;
+        this.newStatus = newStatus;
     }
 
-    public void setOldStatus(String oldStatus) {
+    public void setOldStatus(UserTaskState oldStatus) {
         this.oldStatus = oldStatus;
 
     }
 
-    public void setNewStatus(String newStatus) {
+    public void setNewStatus(UserTaskState newStatus) {
         this.newStatus = newStatus;
 
     }
 
     @Override
-    public String getNewStatus() {
+    public UserTaskState getNewStatus() {
         return newStatus;
     }
 
     @Override
-    public String getOldStatus() {
+    public UserTaskState getOldStatus() {
         return oldStatus;
     }
 
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 da681ed8f4..369c7fc6bc 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
@@ -18,34 +18,53 @@
  */
 package org.kie.kogito.usertask.impl.lifecycle;
 
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
+import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
 import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
 import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.lifecycle.UserTaskState.TerminationType;
 import org.kie.kogito.usertask.lifecycle.UserTaskTransition;
+import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException;
 import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken;
 
 public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
+    public static final String WORKFLOW_ENGINE_USER = "WORKFLOW_ENGINE_USER";
 
-    public static final UserTaskState INACTIVE = UserTaskState.of(null);
+    public static final String PARAMETER_USER = "USER";
+    public static final String PARAMETER_NOTIFY = "NOTIFY";
+
+    public static final String ACTIVATE = "activate";
+    public static final String CLAIM = "claim";
+    public static final String RELEASE = "release";
+    public static final String COMPLETE = "complete";
+    public static final String SKIP = "skip";
+    public static final String FAIL = "fail";
+
+    public static final UserTaskState INACTIVE = UserTaskState.initalized();
     public static final UserTaskState ACTIVE = UserTaskState.of("Ready");
     public static final UserTaskState RESERVED = UserTaskState.of("Reserved");
     public static final UserTaskState COMPLETED = UserTaskState.of("Completed", TerminationType.COMPLETED);
     public static final UserTaskState ERROR = UserTaskState.of("Error", TerminationType.ERROR);
     public static final UserTaskState OBSOLETE = UserTaskState.of("Obsolete", TerminationType.OBSOLETE);
 
-    private static final UserTaskTransition T_NEW_ACTIVE = new DefaultUserTransition("activate", INACTIVE, ACTIVE, DefaultUserTaskLifeCycle::activate);
-    private static final UserTaskTransition T_ACTIVE_RESERVED = new DefaultUserTransition("claim", ACTIVE, RESERVED, DefaultUserTaskLifeCycle::claim);
-    private static final UserTaskTransition T_RESERVED_ACTIVE = new DefaultUserTransition("release", RESERVED, ACTIVE, DefaultUserTaskLifeCycle::release);
-    private static final UserTaskTransition T_ACTIVE_COMPLETED = new DefaultUserTransition("complete", ACTIVE, COMPLETED, DefaultUserTaskLifeCycle::complete);
-    private static final UserTaskTransition T_RESERVED_COMPLETED = new DefaultUserTransition("complete", RESERVED, COMPLETED, DefaultUserTaskLifeCycle::complete);
-    private static final UserTaskTransition T_RESERVED_SKIPPED = new DefaultUserTransition("skip", RESERVED, OBSOLETE, DefaultUserTaskLifeCycle::skip);
-    private static final UserTaskTransition T_ACTIVE_SKIPPED = new DefaultUserTransition("skip", ACTIVE, OBSOLETE, DefaultUserTaskLifeCycle::complete);
-    private static final UserTaskTransition T_RESERVED_ERROR = new DefaultUserTransition("fail", RESERVED, ERROR, DefaultUserTaskLifeCycle::fail);
+    private final UserTaskTransition T_NEW_ACTIVE = new DefaultUserTransition(ACTIVATE, INACTIVE, ACTIVE, this::activate);
+    private final UserTaskTransition T_ACTIVE_RESERVED = new DefaultUserTransition(CLAIM, ACTIVE, RESERVED, this::claim);
+    private final UserTaskTransition T_ACTIVE_SKIPPED = new DefaultUserTransition(SKIP, ACTIVE, OBSOLETE, this::skip);
+    private final UserTaskTransition T_ACTIVE_ERROR = new DefaultUserTransition(FAIL, ACTIVE, ERROR, this::fail);
+    private final UserTaskTransition T_RESERVED_ACTIVE = new DefaultUserTransition(RELEASE, RESERVED, ACTIVE, this::release);
+    private final UserTaskTransition T_RESERVED_COMPLETED = new DefaultUserTransition(COMPLETE, RESERVED, COMPLETED, this::complete);
+    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 List<UserTaskTransition> transitions;
 
@@ -53,58 +72,140 @@ public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle {
         transitions = List.of(
                 T_NEW_ACTIVE,
                 T_ACTIVE_RESERVED,
+                T_ACTIVE_SKIPPED,
+                T_ACTIVE_ERROR,
                 T_RESERVED_ACTIVE,
-                T_ACTIVE_COMPLETED,
                 T_RESERVED_COMPLETED,
-                T_ACTIVE_SKIPPED,
                 T_RESERVED_SKIPPED,
                 T_RESERVED_ERROR);
     }
 
     @Override
-    public Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition) {
-        return Optional.empty();
+    public List<UserTaskTransition> allowedTransitions(UserTaskInstance userTaskInstance) {
+        return transitions.stream().filter(t -> t.source().equals(userTaskInstance.getStatus())).toList();
     }
 
     @Override
-    public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map<String, Object> data) {
-        UserTaskState state = userTaskInstance.getStatus();
-        UserTaskTransition transition = transitions.stream().filter(e -> e.source().equals(state) && e.id().equals(transitionId)).findAny()
-                .orElseThrow(() -> new RuntimeException("Invalid transition " + transitionId + " from " + state));
-        return new DefaultUserTaskTransitionToken(transition, data);
+    public Optional<UserTaskTransitionToken> transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken userTaskTransitionToken, IdentityProvider identityProvider) {
+        checkPermission(userTaskInstance, identityProvider);
+        UserTaskTransition transition = transitions.stream()
+                .filter(t -> t.source().equals(userTaskInstance.getStatus()) && t.id().equals(userTaskTransitionToken.transitionId()))
+                .findFirst()
+                .orElseThrow(() -> new UserTaskTransitionException("Invalid transition from " + userTaskInstance.getStatus()));
+        return transition.executor().execute(userTaskInstance, userTaskTransitionToken, identityProvider);
     }
 
     @Override
     public UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data) {
-        return newTransitionToken("complete", userTaskInstance, data);
+        return newTransitionToken(COMPLETE, userTaskInstance.getStatus(), data);
     }
 
     @Override
     public UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map<String, Object> data) {
-        return newTransitionToken("fail", userTaskInstance, data);
+        return newTransitionToken(FAIL, userTaskInstance.getStatus(), data);
     }
 
-    public static Optional<UserTaskTransitionToken> activate(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    @Override
+    public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map<String, Object> data) {
+        return newTransitionToken(transitionId, userTaskInstance.getStatus(), data);
+    }
+
+    public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskState state, Map<String, Object> data) {
+        UserTaskTransition transition = transitions.stream().filter(e -> e.source().equals(state) && e.id().equals(transitionId)).findAny()
+                .orElseThrow(() -> new RuntimeException("Invalid transition " + transitionId + " from " + state));
+        return new DefaultUserTaskTransitionToken(transition.id(), transition.source(), transition.target(), data);
+    }
+
+    public Optional<UserTaskTransitionToken> activate(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        String user = assignStrategy(userTaskInstance, identityProvider);
+        if (user != null) {
+            return Optional.of(newTransitionToken(CLAIM, ACTIVE, Map.of(PARAMETER_USER, user)));
+        }
         return Optional.empty();
     }
 
-    public static Optional<UserTaskTransitionToken> claim(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    public Optional<UserTaskTransitionToken> claim(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) {
+            if (token.data().containsKey(PARAMETER_USER)) {
+                defaultUserTaskInstance.setActuaOwner((String) token.data().get(PARAMETER_USER));
+            } else {
+                defaultUserTaskInstance.setActuaOwner(identityProvider.getName());
+            }
+        }
         return Optional.empty();
     }
 
-    public static Optional<UserTaskTransitionToken> release(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    public Optional<UserTaskTransitionToken> release(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) {
+            defaultUserTaskInstance.setActuaOwner(null);
+        }
         return Optional.empty();
     }
 
-    public static Optional<UserTaskTransitionToken> complete(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    public Optional<UserTaskTransitionToken> complete(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        token.data().forEach(userTaskInstance::setOutput);
         return Optional.empty();
     }
 
-    public static Optional<UserTaskTransitionToken> skip(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    public Optional<UserTaskTransitionToken> skip(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        if (token.data().containsKey(PARAMETER_NOTIFY)) {
+            userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY));
+        }
         return Optional.empty();
     }
 
-    public static Optional<UserTaskTransitionToken> fail(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) {
+    public Optional<UserTaskTransitionToken> fail(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
+        if (token.data().containsKey(PARAMETER_NOTIFY)) {
+            userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY));
+        }
         return Optional.empty();
     }
+
+    private String assignStrategy(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) {
+        UserTaskAssignmentStrategy assignmentStrategy = userTaskInstance.getUserTask().getAssignmentStrategy();
+        return assignmentStrategy.computeAssigment(userTaskInstance, identityProvider).orElse(null);
+    }
+
+    private void checkPermission(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) {
+        String user = identityProvider.getName();
+        Collection<String> roles = identityProvider.getRoles();
+
+        if (WORKFLOW_ENGINE_USER.equals(user)) {
+            return;
+        }
+
+        // first we check admins
+        Set<String> adminUsers = userTaskInstance.getAdminUsers();
+        if (adminUsers.contains(user)) {
+            return;
+        }
+
+        Set<String> userAdminGroups = new HashSet<>(userTaskInstance.getAdminGroups());
+        userAdminGroups.retainAll(roles);
+        if (!userAdminGroups.isEmpty()) {
+            return;
+        }
+
+        if (userTaskInstance.getActualOwner() != null && userTaskInstance.getActualOwner().equals(user)) {
+            return;
+        }
+
+        if (List.of(INACTIVE, ACTIVE).contains(userTaskInstance.getStatus())) {
+            // there is no user
+            Set<String> users = new HashSet<>(userTaskInstance.getPotentialUsers());
+            users.removeAll(userTaskInstance.getExcludedUsers());
+            if (users.contains(identityProvider.getName())) {
+                return;
+            }
+
+            Set<String> userPotGroups = new HashSet<>(userTaskInstance.getPotentialGroups());
+            userPotGroups.retainAll(roles);
+            if (!userPotGroups.isEmpty()) {
+                return;
+            }
+        }
+
+        throw new NotAuthorizedException("user " + user + " with roles " + roles + " not autorized to perform an operation on user task " + userTaskInstance.getId());
+    }
+
 }
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
index 303d9d8e80..f8cfd7733f 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
@@ -20,21 +20,25 @@ package org.kie.kogito.usertask.impl.lifecycle;
 
 import java.util.Map;
 
-import org.kie.kogito.usertask.lifecycle.UserTaskTransition;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
 import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken;
 
 public class DefaultUserTaskTransitionToken implements UserTaskTransitionToken {
 
-    private UserTaskTransition transition;
+    private String transition;
     private Map<String, Object> data;
+    private UserTaskState source;
+    private UserTaskState target;
 
-    public DefaultUserTaskTransitionToken(UserTaskTransition transition, Map<String, Object> data) {
+    public DefaultUserTaskTransitionToken(String transition, UserTaskState source, UserTaskState target, Map<String, Object> data) {
         this.transition = transition;
+        this.source = source;
+        this.target = target;
         this.data = data;
     }
 
     @Override
-    public UserTaskTransition transition() {
+    public String transitionId() {
         return transition;
     }
 
@@ -43,4 +47,14 @@ public class DefaultUserTaskTransitionToken implements UserTaskTransitionToken {
         return data;
     }
 
+    @Override
+    public UserTaskState source() {
+        return source;
+    }
+
+    @Override
+    public UserTaskState target() {
+        return target;
+    }
+
 }
diff --git a/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java b/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java
index 993fb8f8ad..21ba12d2d1 100644
--- a/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java
+++ b/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java
@@ -45,6 +45,11 @@ public class DefaultKogitoWorkItemHandler implements KogitoWorkItemHandler {
     public static final String TRANSITION_ACTIVATE = "activate";
     public static final String TRANSITION_SKIP = "skip";
 
+    public static final WorkItemPhaseState INITIALIZED = WorkItemPhaseState.initialized();
+    public static final WorkItemPhaseState COMPLETED = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE);
+    public static final WorkItemPhaseState ABORTED = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT);
+    public static final WorkItemPhaseState ACTIVATED = WorkItemPhaseState.of("Activated");
+
     private static Logger LOG = LoggerFactory.getLogger(DefaultKogitoWorkItemHandler.class);
 
     protected Application application;
@@ -60,15 +65,10 @@ public class DefaultKogitoWorkItemHandler implements KogitoWorkItemHandler {
     }
 
     public WorkItemLifeCycle initialize() {
-        WorkItemPhaseState initialized = WorkItemPhaseState.initialized();
-        WorkItemPhaseState completed = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE);
-        WorkItemPhaseState aborted = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT);
-        WorkItemPhaseState activated = WorkItemPhaseState.of("Activated");
-
-        DefaultWorkItemLifeCyclePhase complete = new DefaultWorkItemLifeCyclePhase(TRANSITION_COMPLETE, activated, completed, this::completeWorkItemHandler);
-        DefaultWorkItemLifeCyclePhase abort = new DefaultWorkItemLifeCyclePhase(TRANSITION_ABORT, activated, aborted, this::abortWorkItemHandler);
-        DefaultWorkItemLifeCyclePhase active = new DefaultWorkItemLifeCyclePhase(TRANSITION_ACTIVATE, initialized, activated, this::activateWorkItemHandler);
-        DefaultWorkItemLifeCyclePhase skip = new DefaultWorkItemLifeCyclePhase(TRANSITION_SKIP, activated, completed, this::skipWorkItemHandler);
+        DefaultWorkItemLifeCyclePhase complete = new DefaultWorkItemLifeCyclePhase(TRANSITION_COMPLETE, ACTIVATED, COMPLETED, this::completeWorkItemHandler);
+        DefaultWorkItemLifeCyclePhase abort = new DefaultWorkItemLifeCyclePhase(TRANSITION_ABORT, ACTIVATED, ABORTED, this::abortWorkItemHandler);
+        DefaultWorkItemLifeCyclePhase active = new DefaultWorkItemLifeCyclePhase(TRANSITION_ACTIVATE, INITIALIZED, ACTIVATED, this::activateWorkItemHandler);
+        DefaultWorkItemLifeCyclePhase skip = new DefaultWorkItemLifeCyclePhase(TRANSITION_SKIP, ACTIVATED, COMPLETED, this::skipWorkItemHandler);
         return new DefaultWorkItemLifeCycle(active, skip, abort, complete);
     }
 
@@ -83,17 +83,17 @@ public class DefaultKogitoWorkItemHandler implements KogitoWorkItemHandler {
 
     @Override
     public WorkItemTransition startingTransition(Map<String, Object> data, Policy... policies) {
-        return workItemLifeCycle.newTransition("activate", null, data, policies);
+        return workItemLifeCycle.newTransition(TRANSITION_ACTIVATE, null, data, policies);
     }
 
     @Override
     public WorkItemTransition abortTransition(String phaseStatus, Policy... policies) {
-        return workItemLifeCycle.newTransition("abort", phaseStatus, emptyMap(), policies);
+        return workItemLifeCycle.newTransition(TRANSITION_ABORT, phaseStatus, emptyMap(), policies);
     }
 
     @Override
     public WorkItemTransition completeTransition(String phaseStatus, Map<String, Object> data, Policy... policies) {
-        return workItemLifeCycle.newTransition("complete", phaseStatus, data, policies);
+        return workItemLifeCycle.newTransition(TRANSITION_COMPLETE, phaseStatus, data, policies);
     }
 
     @Override
@@ -121,11 +121,11 @@ public class DefaultKogitoWorkItemHandler implements KogitoWorkItemHandler {
         return Optional.empty();
     }
 
-    public Optional<WorkItemTransition> abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) {
+    public Optional<WorkItemTransition> completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) {
         return Optional.empty();
     }
 
-    public Optional<WorkItemTransition> completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) {
+    public Optional<WorkItemTransition> abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) {
         return Optional.empty();
     }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
index 39efe72339..5284672e0a 100644
--- a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
+++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
@@ -149,8 +149,11 @@ public class PublishEventIT extends AbstractCodegenIT {
 
     }
 
-    private Optional<UserTaskInstanceStateDataEvent> findUserTaskInstanceEvent(List<DataEvent<?>> events, String status) {
-        return events.stream().filter(UserTaskInstanceStateDataEvent.class::isInstance).map(e -> (UserTaskInstanceStateDataEvent) e).filter(e -> status.equals(e.getData().getState())).findAny();
+    private Optional<UserTaskInstanceStateDataEvent> findUserTaskInstanceEvent(List<DataEvent<?>> events, String taskName, String status) {
+        return events.stream().filter(UserTaskInstanceStateDataEvent.class::isInstance)
+                .map(e -> (UserTaskInstanceStateDataEvent) e)
+                .filter(e -> status.equals(e.getData().getState()) && e.getData().getUserTaskName().equals(taskName))
+                .findAny();
     }
 
     private Optional<DataEvent<?>> findProcessInstanceEvent(List<DataEvent<?>> events, int state) {
@@ -205,7 +208,7 @@ public class PublishEventIT extends AbstractCodegenIT {
         List<ProcessInstanceNodeEventBody> left = findNodeInstanceEvents(events, 2);
         assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("StartNode");
 
-        Optional<UserTaskInstanceStateDataEvent> userFirstTask = findUserTaskInstanceEvent(events, "Ready");
+        Optional<UserTaskInstanceStateDataEvent> userFirstTask = findUserTaskInstanceEvent(events, "FirstTask", "Ready");
         assertThat(userFirstTask).isPresent();
         assertUserTaskInstanceEvent(userFirstTask.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task");
 
@@ -226,11 +229,12 @@ public class PublishEventIT extends AbstractCodegenIT {
         left = findNodeInstanceEvents(events, 1);
         assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("HumanTaskNode");
 
-        Optional<UserTaskInstanceStateDataEvent> firstUserTaskInstance = findUserTaskInstanceEvent(events, "Ready");
-        Optional<UserTaskInstanceStateDataEvent> secondUserTaskInstance = findUserTaskInstanceEvent(events, "Completed");
+        Optional<UserTaskInstanceStateDataEvent> firstUserTaskInstance = findUserTaskInstanceEvent(events, "SecondTask", "Ready");
+        // we completed through the work item handler so the user task is really obsolete
+        Optional<UserTaskInstanceStateDataEvent> secondUserTaskInstance = findUserTaskInstanceEvent(events, "FirstTask", "Obsolete");
 
         assertUserTaskInstanceEvent(firstUserTaskInstance.get(), "SecondTask", null, "1", "Ready", "UserTasksProcess", "Second Task");
-        assertUserTaskInstanceEvent(secondUserTaskInstance.get(), "FirstTask", null, "1", "Completed", "UserTasksProcess", "First Task");
+        assertUserTaskInstanceEvent(secondUserTaskInstance.get(), "FirstTask", null, "1", "Obsolete", "UserTasksProcess", "First Task");
 
         workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
@@ -252,7 +256,7 @@ public class PublishEventIT extends AbstractCodegenIT {
         left = findNodeInstanceEvents(events, 2);
         assertThat(left).hasSize(2).extractingResultOf("getNodeType").containsOnly("HumanTaskNode", "EndNode");
 
-        assertUserTaskInstanceEvent(events.get(0), "SecondTask", null, "1", "Completed", "UserTasksProcess", "Second Task");
+        assertUserTaskInstanceEvent(events.get(0), "SecondTask", null, "1", "Obsolete", "UserTasksProcess", "Second Task");
     }
 
     @Test
@@ -285,7 +289,7 @@ public class PublishEventIT extends AbstractCodegenIT {
         List<ProcessInstanceNodeEventBody> triggered = findNodeInstanceEvents(events, 1);
         assertThat(triggered).hasSize(2).extractingResultOf("getNodeName").containsOnly("StartProcess", "First Task");
 
-        Optional<UserTaskInstanceStateDataEvent> event = findUserTaskInstanceEvent(events, "Ready");
+        Optional<UserTaskInstanceStateDataEvent> event = findUserTaskInstanceEvent(events, "FirstTask", "Ready");
         assertThat(event).isPresent();
         assertUserTaskInstanceEvent(event.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task");
 
@@ -343,7 +347,7 @@ public class PublishEventIT extends AbstractCodegenIT {
         List<ProcessInstanceNodeEventBody> left = findNodeInstanceEvents(events, 2);
         assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("StartNode");
 
-        Optional<UserTaskInstanceStateDataEvent> userTask = findUserTaskInstanceEvent(events, "Ready");
+        Optional<UserTaskInstanceStateDataEvent> userTask = findUserTaskInstanceEvent(events, "FirstTask", "Ready");
         assertThat(userTask).isPresent();
         assertUserTaskInstanceEvent(userTask.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task");
     }
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 dc15d15ae9..7a0e493fa4 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
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.Application;
 import org.kie.kogito.Model;
@@ -38,31 +37,32 @@ import org.kie.kogito.codegen.data.Person;
 import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener;
 import org.kie.kogito.internal.process.event.ProcessWorkItemTransitionEvent;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
-import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
 import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
 import org.kie.kogito.internal.process.workitem.Policy;
-import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException;
-import org.kie.kogito.internal.process.workitem.WorkItemTransition;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener;
 import org.kie.kogito.process.Process;
 import org.kie.kogito.process.ProcessConfig;
 import org.kie.kogito.process.ProcessInstance;
 import org.kie.kogito.process.Processes;
 import org.kie.kogito.process.VariableViolationException;
 import org.kie.kogito.process.WorkItem;
+import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTasks;
+import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptyMap;
 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.AssertionsForClassTypes.assertThatExceptionOfType;
-import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.ACTIVATED;
-import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.RESERVED;
-import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_ACTIVATED_CLAIM;
-import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_RESERVED_COMPLETE;
-import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_RESERVED_RELEASE;
-
-@Disabled
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+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;
+
 public class UserTaskIT extends AbstractCodegenIT {
 
     private Policy securityPolicy = SecurityPolicy.of("john", emptyList());
@@ -86,6 +86,9 @@ public class UserTaskIT extends AbstractCodegenIT {
             }
         });
 
+        // we wired user tasks and processes
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
+
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -100,28 +103,55 @@ public class UserTaskIT extends AbstractCodegenIT {
         List<WorkItem> workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         assertThat(workItems.get(0).getName()).isEqualTo("FirstTask");
-        WorkItem wi = workItems.get(0);
+        WorkItem wi_1 = workItems.get(0);
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+
+        UserTask userTask_1 = userTasks.userTaskById(getUserTaskId(wi_1.getExternalReferenceId()));
+        UserTaskInstance userTaskInstance_1 = userTask_1.instances().findById(getUserTaskInstanceId(wi_1.getExternalReferenceId())).get();
+        assertThat(userTaskInstance_1).isNotNull();
+
+        List<UserTaskInstance> userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("mary"));
+        assertThat(userTaskList).hasSize(1);
+
+        userTaskList = userTask_1.instances().findByIdentity(IdentityProviders.of("invalid"));
+        assertThat(userTaskList).hasSize(0);
+
+        userTaskList = userTask_1.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskList).hasSize(1);
+
+        userTaskInstance_1 = userTaskList.get(0);
+        userTaskInstance_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        userTaskInstance_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
         workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         assertThat(workItems.get(0).getName()).isEqualTo("SecondTask");
+        WorkItem wi_2 = workItems.get(0);
+
+        UserTask userTask_2 = userTasks.userTaskById(getUserTaskId(wi_2.getExternalReferenceId()));
+        UserTaskInstance userTaskInstance_2 = userTask_2.instances().findById(getUserTaskInstanceId(wi_2.getExternalReferenceId())).get();
+        assertThat(userTaskInstance_2).isNotNull();
+
+        userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskList).hasSize(1);
+
+        userTaskInstance_1 = userTaskList.get(0);
+        userTaskInstance_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
-        processInstance.completeWorkItem(workItems.get(0).getId(), null, securityPolicy);
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
 
-        assertThat(workItemTransitionEvents).hasSize(12);
+        assertThat(workItemTransitionEvents).hasSize(8);
+    }
+
+    private String getUserTaskInstanceId(String externalReference) {
+        return externalReference.split(":")[1];
+    }
+
+    private String getUserTaskId(String externalReference) {
+        return externalReference.split(":")[0];
     }
 
     @Test
@@ -129,7 +159,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -146,16 +176,12 @@ public class UserTaskIT extends AbstractCodegenIT {
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
@@ -164,9 +190,11 @@ public class UserTaskIT extends AbstractCodegenIT {
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
 
-        handler = getWorkItemHandler(p, wi);
-        transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
+
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
     }
 
@@ -175,7 +203,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -193,32 +221,20 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(wi.getName()).isEqualTo("FirstTask");
         assertThat(wi.getResults()).isEmpty();
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-
-        assertThat(wi.getResults()).hasSize(1)
-                .containsEntry("test", "value");
-
-        handler = getWorkItemHandler(p, wi);
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
+        ut_1.transition(COMPLETE, Map.of("test", "value"), IdentityProviders.of("john"));
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
 
         processInstance.abort();
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
@@ -229,7 +245,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -245,43 +261,26 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
-
-        WorkItemTransition claim = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), claim);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+        assertThat(ut_1.getActualOwner()).isEqualTo("john");
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
+        ut_1.transition(RELEASE, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE);
+        assertThat(ut_1.getActualOwner()).isNull();
 
-        WorkItemTransition release = handler.newTransition(TRANSITION_RESERVED_RELEASE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), release);
+        ut_1.transition(DefaultUserTaskLifeCycle.CLAIM, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+        assertThat(ut_1.getActualOwner()).isEqualTo("john");
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
-        assertThat(wi.getResults()).isEmpty();
-
-        claim = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), claim);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
-        WorkItemTransition transition = handler.completeTransition(wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), transition);
+        ut_1.transition(DefaultUserTaskLifeCycle.COMPLETE, emptyMap(), IdentityProviders.of("john"));
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
         workItems = processInstance.workItems(securityPolicy);
@@ -289,11 +288,14 @@ public class UserTaskIT extends AbstractCodegenIT {
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
 
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
 
         processInstance.abort();
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
+        assertThat(userTasks.instances().findByIdentity(IdentityProviders.of("john"))).hasSize(0);
     }
 
     @Test
@@ -301,6 +303,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         final List<String> workItemTransitionEvents = new ArrayList<>();
         app.config().get(ProcessConfig.class).processEventListeners().listeners().add(new DefaultKogitoProcessEventListener() {
 
@@ -330,37 +333,30 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).hasSize(1)
-                .containsEntry("test", "value");
-
-        transition = handler.completeTransition(wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.COMPLETED);
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
 
         processInstance.abort();
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
 
-        assertThat(workItemTransitionEvents).hasSize(12);
+        assertThat(workItemTransitionEvents).hasSize(8);
     }
 
     @Test
@@ -368,7 +364,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -384,7 +380,7 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
         final String wiId = wi.getId();
@@ -394,11 +390,13 @@ public class UserTaskIT extends AbstractCodegenIT {
         List<WorkItem> securedWorkItems = processInstance.workItems(SecurityPolicy.of(identity));
         assertThat(securedWorkItems).isEmpty();
 
-        assertThatExceptionOfType(WorkItemNotFoundException.class).isThrownBy(() -> processInstance.workItem(wiId, SecurityPolicy.of(identity)));
+        assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> processInstance.workItem(wiId, SecurityPolicy.of(identity)));
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition claimKelly = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "kelly"), SecurityPolicy.of(identity));
-        assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> processInstance.transitionWorkItem(wiId, claimKelly));
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance utInvalid = userTaskInstances.get(0);
+        assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> utInvalid.transition(CLAIM, emptyMap(), IdentityProviders.of("invalid")));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
@@ -406,32 +404,32 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        WorkItemTransition claimJohn = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(wiId, claimJohn);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).isEmpty();
-        WorkItemTransition completeJohn = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(wiId, completeJohn);
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+
         processInstance.abort();
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
+        assertThat(userTasks.instances().findByIdentity(IdentityProviders.of("john"))).hasSize(0);
     }
 
     @Test
@@ -439,7 +437,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("approvals");
 
         Model m = p.createModel();
@@ -458,10 +456,12 @@ public class UserTaskIT extends AbstractCodegenIT {
         List<WorkItem> workItems = processInstance.workItems(policy);
         assertThat(workItems).hasSize(1);
 
-        WorkItem wi = workItems.get(0);
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ActorId", "manager"), policy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("manager"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("manager"));
+
         // actual owner of the first task is excluded owner on the second task so won't find it
         workItems = processInstance.workItems(policy);
         assertThat(workItems).isEmpty();
@@ -471,15 +471,12 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         workItems = processInstance.workItems(policy);
         assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), policy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
 
-        workItems = processInstance.workItems(policy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), policy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("admin", singletonList("managers")));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        ut_2.transition(CLAIM, emptyMap(), IdentityProviders.of("admin", singletonList("managers")));
+        ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("admin", singletonList("managers")));
 
         assertThat(processInstance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
     }
@@ -489,7 +486,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("approvals");
 
         Model m = p.createModel();
@@ -519,13 +516,13 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
 
         assertThat(workItems).hasSize(1);
-        WorkItem wi = workItems.get(0);
-
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), policy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
 
-        processInstance.completeWorkItem(workItems.get(0).getId(), null, policy);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(identity);
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), identity);
+        ut_1.transition(COMPLETE, emptyMap(), identity);
 
         assertThat(processInstance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
     }
@@ -535,7 +532,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -547,30 +544,20 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        List<WorkItem> workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        WorkItem wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
-
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        workItems = processInstance.workItems(securityPolicy);
+        List<WorkItem> workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
+        WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
 
         String firstSecondTaskNodeInstanceId = wi.getNodeInstanceId();
 
@@ -582,11 +569,14 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         // since it was triggered again it must have different node instance id
         assertThat(wi.getNodeInstanceId()).isNotEqualTo(firstSecondTaskNodeInstanceId);
-        transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
     }
@@ -596,7 +586,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -613,18 +603,14 @@ public class UserTaskIT extends AbstractCodegenIT {
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
 
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
@@ -632,8 +618,7 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
-
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
 
         String firstSecondTaskNodeInstanceId = wi.getNodeInstanceId();
 
@@ -645,12 +630,14 @@ public class UserTaskIT extends AbstractCodegenIT {
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
 
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         // since it was retriggered it must have different node instance id
         assertThat(wi.getNodeInstanceId()).isNotEqualTo(firstSecondTaskNodeInstanceId);
 
-        transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), transition);
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
     }
@@ -660,7 +647,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -676,58 +663,36 @@ public class UserTaskIT extends AbstractCodegenIT {
         assertThat(workItems).hasSize(1);
         WorkItem wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+        ut_1.transition(RELEASE, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE);
+        assertThat(ut_1.getActualOwner()).isNull();
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        assertThat(ut_1.getActualOwner()).isEqualTo("john");
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
 
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).hasSize(1)
-                .containsEntry("test", "value");
-
-        transition = handler.newTransition(TRANSITION_RESERVED_RELEASE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy);
-
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName());
-        assertThat(wi.getResults()).hasSize(0);
-
-        transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), transition);
-        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
-        assertThat(wi.getResults()).hasSize(1)
-                .containsEntry("test", "value");
-
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy);
-        processInstance.transitionWorkItem(wi.getId(), transition);
-        assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
-
         workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         wi = workItems.get(0);
         assertThat(wi.getName()).isEqualTo("SecondTask");
-        assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName());
+        assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName());
         assertThat(wi.getResults()).isEmpty();
 
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED);
+
         processInstance.abort();
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
     }
@@ -738,7 +703,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval-with-readonly-variable-tags.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Class<?> resourceClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader());
         assertThat(resourceClazz).isNotNull();
 
@@ -774,7 +739,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval-with-internal-variable-tags.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Class<?> resourceClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader());
         assertThat(resourceClazz).isNotNull();
         // internal variables are not exposed on the model
@@ -800,7 +765,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval-with-required-variable-tags.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("approvals");
 
         Model m = p.createModel();
@@ -818,7 +783,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/approval-with-io-variable-tags.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Class<?> modelClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader());
         assertThat(modelClazz).isNotNull();
         assertThat(modelClazz.getDeclaredField("decision")).isNotNull();
@@ -857,7 +822,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTaskWithIOexpression.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTask");
 
         Model m = p.createModel();
@@ -888,7 +853,7 @@ public class UserTaskIT extends AbstractCodegenIT {
 
         Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
         assertThat(app).isNotNull();
-
+        app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class)));
         Process<? extends Model> p = app.get(Processes.class).processById("UserTasksProcess");
 
         Model m = p.createModel();
@@ -912,24 +877,23 @@ public class UserTaskIT extends AbstractCodegenIT {
         List<WorkItem> workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         assertThat(workItems.get(0).getName()).isEqualTo("FirstTask");
-        WorkItem wi = workItems.get(0);
 
-        KogitoWorkItemHandler handler = getWorkItemHandler(p, wi);
-        WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
-
-        workItems = processInstance.workItems(securityPolicy);
-        assertThat(workItems).hasSize(1);
-        wi = workItems.get(0);
-        assertThat(wi.getName()).isEqualTo("FirstTask");
-        transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy);
-        processInstance.transitionWorkItem(workItems.get(0).getId(), transition);
+        UserTasks userTasks = app.get(UserTasks.class);
+        List<UserTaskInstance> userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_1 = userTaskInstances.get(0);
+        ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john"));
+        ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
 
         workItems = processInstance.workItems(securityPolicy);
         assertThat(workItems).hasSize(1);
         assertThat(workItems.get(0).getName()).isEqualTo("SecondTask");
 
-        processInstance.completeWorkItem(workItems.get(0).getId(), null, securityPolicy);
+        userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john"));
+        assertThat(userTaskInstances).isNotNull().hasSize(1);
+        UserTaskInstance ut_2 = userTaskInstances.get(0);
+        ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john"));
+
         assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
     }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
index 71debb2806..86e6bd8053 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
@@ -42,7 +42,7 @@ import org.jbpm.compiler.canonical.ModelMetaData;
 import org.jbpm.compiler.canonical.ProcessMetaData;
 import org.jbpm.compiler.canonical.ProcessToExecModelGenerator;
 import org.jbpm.compiler.canonical.TriggerMetaData;
-import org.jbpm.compiler.canonical.UserTaskModelMetaData;
+import org.jbpm.compiler.canonical.WorkItemModelMetaData;
 import org.jbpm.compiler.xml.XmlProcessReader;
 import org.jbpm.compiler.xml.core.SemanticModules;
 import org.jbpm.process.core.impl.ProcessImpl;
@@ -288,7 +288,7 @@ public class ProcessCodegen extends AbstractGenerator {
         Map<String, InputModelClassGenerator> processIdToInputModelGenerator = new HashMap<>();
         Map<String, OutputModelClassGenerator> processIdToOutputModelGenerator = new HashMap<>();
 
-        Map<String, List<UserTaskModelMetaData>> processIdToUserTaskModel = new HashMap<>();
+        Map<String, List<WorkItemModelMetaData>> processIdToWorkItemModel = new HashMap<>();
         Map<String, ProcessMetaData> processIdToMetadata = new HashMap<>();
 
         // first we generate all the data classes from variable declarations
@@ -305,10 +305,13 @@ public class ProcessCodegen extends AbstractGenerator {
             }
         }
 
-        // then we generate user task inputs and outputs if any
+        // then we generate work items task inputs and outputs if any
         for (WorkflowProcess workFlowProcess : processes.values()) {
-            UserTasksModelClassGenerator utcg = new UserTasksModelClassGenerator(workFlowProcess);
-            processIdToUserTaskModel.put(workFlowProcess.getId(), utcg.generate());
+            if (KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType())) {
+                continue;
+            }
+            WorkItemModelClassGenerator utcg = new WorkItemModelClassGenerator(workFlowProcess);
+            processIdToWorkItemModel.put(workFlowProcess.getId(), utcg.generate());
         }
 
         // then we can instantiate the exec model generator
@@ -363,7 +366,7 @@ public class ProcessCodegen extends AbstractGenerator {
                         applicationCanonicalName());
 
                 processResourceGenerator
-                        .withUserTasks(processIdToUserTaskModel.get(workFlowProcess.getId()))
+                        .withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId()))
                         .withSignals(metaData.getSignals())
                         .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers())
                         .withTransaction(isTransactionEnabled());
@@ -421,14 +424,14 @@ public class ProcessCodegen extends AbstractGenerator {
                     mmd.generate());
         }
 
-        for (List<UserTaskModelMetaData> utmd : processIdToUserTaskModel.values()) {
+        for (List<WorkItemModelMetaData> utmd : processIdToWorkItemModel.values()) {
 
-            for (UserTaskModelMetaData ut : utmd) {
-                storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getInputModelClassName()), ut.generateInput());
+            for (WorkItemModelMetaData ut : utmd) {
+                storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getInputModelClassName()), ut.generateInput());
 
-                storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getOutputModelClassName()), ut.generateOutput());
+                storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getOutputModelClassName()), ut.generateOutput());
 
-                storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getTaskModelClassName()), ut.generateModel());
+                storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getTaskModelClassName()), ut.generateModel());
             }
         }
 
@@ -442,7 +445,7 @@ public class ProcessCodegen extends AbstractGenerator {
             for (ProcessResourceGenerator resourceGenerator : rgs) {
                 storeFile(REST_TYPE, resourceGenerator.generatedFilePath(),
                         resourceGenerator.generate());
-                storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(resourceGenerator.getTaskModelFactoryClassName()), resourceGenerator.getTaskModelFactory());
+                storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(resourceGenerator.getTaskModelFactoryClassName()), resourceGenerator.getTaskModelFactory());
             }
         }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java
index 165477103f..3e2f8765a4 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java
@@ -34,7 +34,7 @@ import org.drools.codegen.common.rest.RestAnnotator;
 import org.drools.util.StringUtils;
 import org.jbpm.compiler.canonical.ProcessToExecModelGenerator;
 import org.jbpm.compiler.canonical.TriggerMetaData;
-import org.jbpm.compiler.canonical.UserTaskModelMetaData;
+import org.jbpm.compiler.canonical.WorkItemModelMetaData;
 import org.jbpm.ruleflow.core.Metadata;
 import org.jbpm.ruleflow.core.RuleFlowProcess;
 import org.jbpm.workflow.core.node.StartNode;
@@ -88,7 +88,7 @@ public class ProcessResourceGenerator {
 
     private static final String REST_TEMPLATE_NAME = "RestResource";
     private static final String REACTIVE_REST_TEMPLATE_NAME = "ReactiveRestResource";
-    private static final String REST_USER_TASK_TEMPLATE_NAME = "RestResourceUserTask";
+    private static final String REST_WORK_ITEM_TEMPLATE_NAME = "RestResourceWorkItem";
     private static final String REST_SIGNAL_TEMPLATE_NAME = "RestResourceSignal";
 
     private static final String SIGNAL_METHOD_PREFFIX = "signal_";
@@ -109,7 +109,7 @@ public class ProcessResourceGenerator {
     private boolean transactionEnabled;
     private List<TriggerMetaData> triggers;
 
-    private List<UserTaskModelMetaData> userTasks;
+    private List<WorkItemModelMetaData> workItems;
     private Map<String, String> signals;
     private CompilationUnit taskModelFactoryUnit;
     private String taskModelFactoryClassName;
@@ -134,8 +134,8 @@ public class ProcessResourceGenerator {
         this.processClazzName = processfqcn;
     }
 
-    public ProcessResourceGenerator withUserTasks(List<UserTaskModelMetaData> userTasks) {
-        this.userTasks = userTasks;
+    public ProcessResourceGenerator withWorkItems(List<WorkItemModelMetaData> userTasks) {
+        this.workItems = userTasks;
         return this;
     }
 
@@ -211,7 +211,7 @@ public class ProcessResourceGenerator {
         taskModelFactoryClass.setName(taskModelFactorySimpleClassName);
         typeInterpolations.put("$TaskModelFactory$", taskModelFactoryClassName);
 
-        manageUserTasks(templateBuilder, template, taskModelFactoryClass, index);
+        manageWorkItems(templateBuilder, template, taskModelFactoryClass, index);
 
         typeInterpolations.put("$Clazz$", resourceClazzName);
         typeInterpolations.put("$Type$", dataClazzName);
@@ -366,13 +366,13 @@ public class ProcessResourceGenerator {
                 });
     }
 
-    protected void manageUserTasks(TemplatedGenerator.Builder templateBuilder, ClassOrInterfaceDeclaration template,
+    protected void manageWorkItems(TemplatedGenerator.Builder templateBuilder, ClassOrInterfaceDeclaration template,
             ClassOrInterfaceDeclaration taskModelFactoryClass, AtomicInteger index) {
-        if (userTasks != null && !userTasks.isEmpty()) {
+        if (workItems != null && !workItems.isEmpty()) {
 
-            CompilationUnit userTaskClazz = templateBuilder.build(context, REST_USER_TASK_TEMPLATE_NAME).compilationUnitOrThrow();
+            CompilationUnit workItemClazz = templateBuilder.build(context, REST_WORK_ITEM_TEMPLATE_NAME).compilationUnitOrThrow();
 
-            ClassOrInterfaceDeclaration userTaskTemplate = userTaskClazz
+            ClassOrInterfaceDeclaration userTaskTemplate = workItemClazz
                     .findFirst(ClassOrInterfaceDeclaration.class)
                     .orElseThrow(() -> new NoSuchElementException("Compilation unit doesn't contain a class or interface declaration!"));
 
@@ -381,8 +381,8 @@ public class ProcessResourceGenerator {
                     .orElseThrow(IllegalStateException::new);
             SwitchStmt switchExpr = taskModelFactoryMethod.getBody().map(b -> b.findFirst(SwitchStmt.class).orElseThrow(IllegalStateException::new)).orElseThrow(IllegalStateException::new);
 
-            for (UserTaskModelMetaData userTask : userTasks) {
-                String methodSuffix = sanitizeName(userTask.getName()) + "_" + index.getAndIncrement();
+            for (WorkItemModelMetaData workItem : workItems) {
+                String methodSuffix = sanitizeName(workItem.getName()) + "_" + index.getAndIncrement();
                 userTaskTemplate.findAll(MethodDeclaration.class).forEach(md -> {
                     MethodDeclaration cloned = md.clone();
                     template.addMethod(cloned.getName() + "_" + methodSuffix, Keyword.PUBLIC)
@@ -392,16 +392,16 @@ public class ProcessResourceGenerator {
                             .setAnnotations(cloned.getAnnotations());
                 });
 
-                template.findAll(StringLiteralExpr.class).forEach(s -> interpolateUserTaskStrings(s, userTask));
-                template.findAll(ClassOrInterfaceType.class).forEach(c -> interpolateUserTaskTypes(c, userTask));
-                template.findAll(NameExpr.class).forEach(c -> interpolateUserTaskNameExp(c, userTask));
-                if (!userTask.isAdHoc()) {
+                template.findAll(StringLiteralExpr.class).forEach(s -> interpolateUserTaskStrings(s, workItem));
+                template.findAll(ClassOrInterfaceType.class).forEach(c -> interpolateUserTaskTypes(c, workItem));
+                template.findAll(NameExpr.class).forEach(c -> interpolateUserTaskNameExp(c, workItem));
+                if (!workItem.isAdHoc()) {
                     template.findAll(MethodDeclaration.class)
                             .stream()
                             .filter(md -> md.getNameAsString().equals(SIGNAL_METHOD_PREFFIX + methodSuffix))
                             .forEach(template::remove);
                 }
-                switchExpr.getEntries().add(0, userTask.getModelSwitchEntry());
+                switchExpr.getEntries().add(0, workItem.getModelSwitchEntry());
             }
 
         }
@@ -479,14 +479,14 @@ public class ProcessResourceGenerator {
         vv.setString(interpolated);
     }
 
-    private void interpolateUserTaskStrings(StringLiteralExpr vv, UserTaskModelMetaData userTask) {
+    private void interpolateUserTaskStrings(StringLiteralExpr vv, WorkItemModelMetaData userTask) {
         String s = vv.getValue();
         String interpolated = s.replace("$taskName$", sanitizeName(userTask.getName()));
         interpolated = interpolated.replace("$taskNodeName$", userTask.getNodeName());
         vv.setString(interpolated);
     }
 
-    private void interpolateUserTaskNameExp(NameExpr name, UserTaskModelMetaData userTask) {
+    private void interpolateUserTaskNameExp(NameExpr name, WorkItemModelMetaData userTask) {
         name.setName(userTask.templateReplacement(name.getNameAsString()));
     }
 
@@ -497,7 +497,7 @@ public class ProcessResourceGenerator {
         m.setName(interpolated);
     }
 
-    private void interpolateUserTaskTypes(Type t, UserTaskModelMetaData userTask) {
+    private void interpolateUserTaskTypes(Type t, WorkItemModelMetaData userTask) {
         if (t.isArrayType()) {
             t = t.asArrayType().getElementType();
         }
@@ -508,11 +508,11 @@ public class ProcessResourceGenerator {
         }
     }
 
-    private void interpolateUserTaskTypes(SimpleName returnType, UserTaskModelMetaData userTask) {
+    private void interpolateUserTaskTypes(SimpleName returnType, WorkItemModelMetaData userTask) {
         returnType.setIdentifier(userTask.templateReplacement(returnType.getIdentifier()));
     }
 
-    private void interpolateUserTaskTypeArguments(NodeList<Type> ta, UserTaskModelMetaData userTask) {
+    private void interpolateUserTaskTypeArguments(NodeList<Type> ta, WorkItemModelMetaData userTask) {
         ta.stream().forEach(t -> interpolateUserTaskTypes(t, userTask));
     }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java
similarity index 81%
rename from kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java
rename to kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java
index a3850d60f1..e66be467ca 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java
@@ -21,21 +21,21 @@ package org.kie.kogito.codegen.process;
 import java.util.List;
 
 import org.jbpm.compiler.canonical.ProcessToExecModelGenerator;
-import org.jbpm.compiler.canonical.UserTaskModelMetaData;
+import org.jbpm.compiler.canonical.WorkItemModelMetaData;
 import org.kie.api.definition.process.WorkflowProcess;
 
-public class UserTasksModelClassGenerator {
+public class WorkItemModelClassGenerator {
 
     private final WorkflowProcess workFlowProcess;
-    private List<UserTaskModelMetaData> modelMetaData;
+    private List<WorkItemModelMetaData> modelMetaData;
 
-    public UserTasksModelClassGenerator(WorkflowProcess workFlowProcess) {
+    public WorkItemModelClassGenerator(WorkflowProcess workFlowProcess) {
         this.workFlowProcess = workFlowProcess;
     }
 
-    public List<UserTaskModelMetaData> generate() {
+    public List<WorkItemModelMetaData> generate() {
         // create model class for all variables
-        modelMetaData = ProcessToExecModelGenerator.INSTANCE.generateUserTaskModel(workFlowProcess);
+        modelMetaData = ProcessToExecModelGenerator.INSTANCE.generateWorkItemModel(workFlowProcess);
         return modelMetaData;
     }
 
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 58bd4a54b6..bd10069d47 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
@@ -18,10 +18,13 @@
  */
 package org.kie.kogito.codegen.usertask;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.Reader;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -74,6 +77,7 @@ import static java.util.stream.Collectors.toList;
 import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.FAIL_ON_ERROR_PROPERTY;
 
 public class UserTaskCodegen extends AbstractGenerator {
+
     private static final String NODE_NAME = "NodeName";
     private static final String DESCRIPTION = "Description";
     private static final String PRIORITY = "Priority";
@@ -97,6 +101,8 @@ public class UserTaskCodegen extends AbstractGenerator {
 
     TemplatedGenerator templateGenerator;
     private List<Work> descriptors;
+    private TemplatedGenerator producerTemplateGenerator;
+    private TemplatedGenerator restTemplateGenerator;
 
     public UserTaskCodegen(KogitoBuildContext context, List<Work> collectedResources) {
         super(context, "usertasks");
@@ -106,6 +112,16 @@ public class UserTaskCodegen extends AbstractGenerator {
                 .withTemplateBasePath("/class-templates/usertask")
                 .withTargetTypeName(SECTION_CLASS_NAME)
                 .build(context, "UserTask");
+
+        producerTemplateGenerator = TemplatedGenerator.builder()
+                .withTemplateBasePath("/class-templates/usertask")
+                .withTargetTypeName(SECTION_CLASS_NAME)
+                .build(context, "UserTasksServiceProducer");
+
+        restTemplateGenerator = TemplatedGenerator.builder()
+                .withTemplateBasePath("/class-templates/usertask")
+                .withTargetTypeName(SECTION_CLASS_NAME)
+                .build(context, "RestResourceUserTask");
     }
 
     @Override
@@ -125,6 +141,42 @@ public class UserTaskCodegen extends AbstractGenerator {
 
     @Override
     protected Collection<GeneratedFile> internalGenerate() {
+        if (descriptors.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<GeneratedFile> generatedFiles = new ArrayList<>();
+        generatedFiles.addAll(generateUserTask());
+        if (context().hasDI()) {
+            generatedFiles.add(generateProducer());
+        }
+
+        if (context().hasRESTForGenerator(this)) {
+            generatedFiles.add(generateRestEndpiont());
+        }
+
+        return generatedFiles;
+    }
+
+    private GeneratedFile generateRestEndpiont() {
+        String packageName = context().getPackageName();
+        CompilationUnit compilationUnit = producerTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks");
+        compilationUnit.setPackageDeclaration(packageName);
+        String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString();
+        String urlBase = packageName.replaceAll("\\.", File.separator);
+        return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString());
+    }
+
+    private GeneratedFile generateProducer() {
+        String packageName = context().getPackageName();
+        CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("No producer template found for user tasks");
+        compilationUnit.setPackageDeclaration(packageName);
+        String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString();
+        String urlBase = packageName.replaceAll("\\.", File.separator);
+        return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString());
+    }
+
+    private List<GeneratedFile> generateUserTask() {
         List<GeneratedFile> generatedFiles = new ArrayList<>();
         for (Work info : descriptors) {
             CompilationUnit unit = templateGenerator.compilationUnit().get();
@@ -171,7 +223,6 @@ public class UserTaskCodegen extends AbstractGenerator {
 
             generatedFiles.add(new GeneratedFile(GeneratedFileType.SOURCE, UserTaskCodegenHelper.path(info).resolve(className + ".java"), unit.toString()));
         }
-
         return generatedFiles;
     }
 
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java
index 641f3b2d1a..d6483f92b2 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java
@@ -35,11 +35,9 @@ import com.github.javaparser.ast.body.ConstructorDeclaration;
 
 public class UserTaskConfigGenerator implements ConfigGenerator {
 
-    private List<Work> collectedResources;
     private TemplatedGenerator templateGenerator;
 
     public UserTaskConfigGenerator(KogitoBuildContext context, List<Work> collectedResources) {
-        this.collectedResources = collectedResources;
         templateGenerator = TemplatedGenerator.builder()
                 .withTemplateBasePath("/class-templates/usertask")
                 .build(context, "UserTaskConfig");
@@ -54,7 +52,6 @@ public class UserTaskConfigGenerator implements ConfigGenerator {
     public GeneratedFile generate() {
         CompilationUnit unit = templateGenerator.compilationUnit().get();
         String packageName = unit.getPackageDeclaration().get().getNameAsString();
-        unit.getPackageDeclaration().get().setName(packageName);
 
         ClassOrInterfaceDeclaration clazzDeclaration = unit.findFirst(ClassOrInterfaceDeclaration.class).get();
         clazzDeclaration.setName(configClassName());
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java
deleted file mode 100644
index 36f93ac462..0000000000
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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 com.myspace.demo;
-
-import java.util.List;
-
-import jakarta.ws.rs.NotFoundException;
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.UriInfo;
-
-import org.kie.kogito.auth.IdentityProviders;
-import org.kie.kogito.auth.SecurityPolicy;
-import org.kie.kogito.process.Process;
-import org.kie.kogito.process.ProcessInstance;
-import org.kie.kogito.process.ProcessInstanceReadMode;
-import org.kie.kogito.process.WorkItem;
-import org.kie.kogito.process.impl.Sig;
-import org.kie.kogito.services.uow.UnitOfWorkExecutor;
-
-public class $Type$Resource {
-
-    @POST
-    @Path("/{id}/$taskName$")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response signal(@PathParam("id") final String id,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            @Context UriInfo uriInfo) {
-        return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups))
-                .map(task -> Response
-                        .created(uriInfo.getAbsolutePathBuilder().path(task.getId()).build())
-                        .entity(task.getResults())
-                        .build())
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @POST
-    @Path("/{id}/$taskName$/{taskId}")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public $Type$Output completeTask(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("phase") @DefaultValue("complete") final String phase,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            final $TaskOutput$ model) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @PUT
-    @Path("/{id}/$taskName$/{taskId}")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public $TaskOutput$ saveTask(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            final $TaskOutput$ model) {
-        return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @POST
-    @Path("/{id}/$taskName$/{taskId}/phases/{phase}")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public $Type$Output taskTransition(
-            @PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("phase") final String phase,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            final $TaskOutput$ model) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public $TaskModel$ getWorkItem(@PathParam("id") String id,
-            @PathParam("taskId") String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @DELETE
-    @Path("/{id}/$taskName$/{taskId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public $Type$Output abortTask(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("phase") @DefaultValue("abort") final String phase,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @GET
-    @Path("$taskName$/schema")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Map<String, Object> getSchema() {
-        return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$");
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}/schema")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Map<String, Object> getSchemaAndPhases(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups));
-    }
-
-    @POST
-    @Path("/{id}/$taskName$/{taskId}/comments")
-    @Consumes(MediaType.TEXT_PLAIN)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response addComment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            String commentInfo,
-            @Context UriInfo uriInfo) {
-        return processService.addComment(process, id, taskId, SecurityPolicy.of(user, groups), commentInfo)
-                .map(comment -> Response.created(uriInfo.getAbsolutePathBuilder().path(comment.getId().toString()).build())
-                        .entity(comment).build())
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @PUT
-    @Path("/{id}/$taskName$/{taskId}/comments/{commentId}")
-    @Consumes(MediaType.TEXT_PLAIN)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Comment updateComment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("commentId") final String commentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            String comment) {
-        return processService.updateComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups), comment)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @DELETE
-    @Path("/{id}/$taskName$/{taskId}/comments/{commentId}")
-    public Response deleteComment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("commentId") final String commentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.deleteComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups))
-                .map(removed -> (removed ? Response.ok() : Response.status(Status.NOT_FOUND)).build())
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @POST
-    @Path("/{id}/$taskName$/{taskId}/attachments")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response addAttachment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            AttachmentInfo attachmentInfo,
-            @Context UriInfo uriInfo) {
-        return processService.addAttachment(process, id, taskId, SecurityPolicy.of(user, groups), attachmentInfo)
-                .map(attachment -> Response
-                        .created(uriInfo.getAbsolutePathBuilder().path(attachment.getId().toString()).build())
-                        .entity(attachment).build())
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @PUT
-    @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Attachment updateAttachment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("attachmentId") final String attachmentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups,
-            AttachmentInfo attachment) {
-        return processService.updateAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups), attachment)
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @DELETE
-    @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}")
-    public Response deleteAttachment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("attachmentId") final String attachmentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.deleteAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups))
-                .map(removed -> (removed ? Response.ok() : Response.status(Status.NOT_FOUND)).build())
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Attachment getAttachment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("attachmentId") final String attachmentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new NotFoundException("Attachment " + attachmentId + " not found"));
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}/attachments")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Collection<Attachment> getAttachments(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getAttachments(process, id, taskId, SecurityPolicy.of(user, groups))
-                .orElseThrow(NotFoundException::new);
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}/comments/{commentId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Comment getComment(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @PathParam("commentId") final String commentId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new NotFoundException("Comment " + commentId + " not found"));
-    }
-
-    @GET
-    @Path("/{id}/$taskName$/{taskId}/comments")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Collection<Comment> getComments(@PathParam("id") final String id,
-            @PathParam("taskId") final String taskId,
-            @QueryParam("user") final String user,
-            @QueryParam("group") final List<String> groups) {
-        return processService.getComments(process, id, taskId, SecurityPolicy.of(user, groups))
-                .orElseThrow(NotFoundException::new);
-    }
-}
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java
deleted file mode 100644
index a18e56c337..0000000000
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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 com.myspace.demo;
-
-import java.util.List;
-import java.util.Map;
-
-import org.jbpm.util.JsonSchemaUtil;
-import org.kie.kogito.auth.IdentityProviders;
-import org.kie.kogito.auth.SecurityPolicy;
-import org.kie.kogito.process.ProcessInstance;
-import org.kie.kogito.process.WorkItem;
-import org.kie.kogito.process.impl.Sig;
-import org.kie.kogito.process.workitem.Comment;
-import org.kie.kogito.process.workitem.TaskMetaInfo;
-import org.kie.kogito.services.uow.UnitOfWorkExecutor;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PatchMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.util.UriComponentsBuilder;
-
-public class $Type$Resource {
-
-    @PostMapping(value = "/{id}/$taskName$", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity signal(@PathVariable("id") final String id,
-            @RequestParam("user") final String user,
-            @RequestParam("group") final List<String> groups,
-            final UriComponentsBuilder uriComponentsBuilder) {
-
-        return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups))
-                .map(task -> ResponseEntity
-                        .created(uriComponentsBuilder
-                                .path("/$name$/{id}/$taskName$/{taskId}")
-                                .buildAndExpand(id, task.getId()).toUri())
-                        .body(task.getResults()))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PostMapping(value = "/{id}/$taskName$/{taskId}/phases/{phase}", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.APPLICATION_JSON_VALUE)
-    public $Type$Output completeTask(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("phase") final String phase,
-            @RequestParam("user") final String user,
-            @RequestParam("group") final List<String> groups,
-            @RequestBody(required = false) final $TaskOutput$ model) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PutMapping(value = "/{id}/$taskName$/{taskId}", consumes = MediaType.APPLICATION_JSON_VALUE)
-    public $TaskOutput$ saveTask(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups,
-            @RequestBody(required = false) final $TaskOutput$ model) {
-        return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PostMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.APPLICATION_JSON_VALUE)
-    public $Type$Output taskTransition(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "phase", required = false,
-                    defaultValue = "complete") final String phase,
-            @RequestParam(value = "user",
-                    required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups,
-            @RequestBody(required = false) final $TaskOutput$ model) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public $TaskModel$ getTask(@PathVariable("id") String id,
-            @PathVariable("taskId") String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups) {
-        return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @DeleteMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public $Type$Output abortTask(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "phase", required = false,
-                    defaultValue = "abort") final String phase,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups) {
-        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @GetMapping(value = "$taskName$/schema", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Map<String, Object> getSchema() {
-        return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$");
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}/schema", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Map<String, Object> getSchemaAndPhases(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups) {
-        return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups));
-    }
-
-    @PostMapping(value = "/{id}/$taskName$/{taskId}/comments", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.TEXT_PLAIN_VALUE)
-    public ResponseEntity<Comment> addComment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups,
-            @RequestBody String commentInfo,
-            UriComponentsBuilder uriComponentsBuilder) {
-        return processService.addComment(process, id, taskId, SecurityPolicy.of(user, groups), commentInfo)
-                .map(comment -> ResponseEntity
-                        .created(uriComponentsBuilder.path("/$name$/{id}/$taskName$/{taskId}/comments/{commentId}")
-                                .buildAndExpand(id, taskId, comment.getId().toString()).toUri())
-                        .body(comment))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PutMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.TEXT_PLAIN_VALUE)
-    public Comment updateComment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("commentId") final String commentId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups,
-            @RequestBody String comment) {
-        return processService.updateComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups), comment)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @DeleteMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}")
-    public ResponseEntity deleteComment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("commentId") final String commentId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups) {
-        return processService.deleteComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups))
-                .map(removed -> (removed ? ResponseEntity.ok().build() : ResponseEntity.notFound().build()))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PostMapping(value = "/{id}/$taskName$/{taskId}/attachments", produces = MediaType.APPLICATION_JSON_VALUE,
-            consumes = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<Attachment> addAttachment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups,
-            @RequestBody AttachmentInfo attachmentInfo,
-            UriComponentsBuilder uriComponentsBuilder) {
-        return processService.addAttachment(process, id, taskId, SecurityPolicy.of(user, groups), attachmentInfo)
-                .map(attachment -> ResponseEntity
-                        .created(uriComponentsBuilder.path(
-                                "/$name$/{id}/$taskName$/{taskId}/attachments/{attachmentId}")
-                                .buildAndExpand(id,
-                                        taskId, attachment.getId())
-                                .toUri())
-                        .body(attachment))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @PutMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}",
-            produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
-    public Attachment updateAttachment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("attachmentId") final String attachmentId,
-            @RequestParam(value = "user",
-                    required = false) final String user,
-            @RequestParam(value = "group",
-                    required = false) final List<String> groups,
-            @RequestBody AttachmentInfo attachment) {
-        return processService.updateAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups), attachment)
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @DeleteMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}")
-    public ResponseEntity deleteAttachment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("attachmentId") final String attachmentId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups) {
-
-        return processService.deleteAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups))
-                .map(removed -> (removed ? ResponseEntity.ok() : ResponseEntity.notFound()).build())
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Attachment getAttachment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("attachmentId") final String attachmentId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups) {
-        return processService.getAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Attachment " + attachmentId + " not found"));
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}/attachments", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Collection<Attachment> getAttachments(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user") final String user,
-            @RequestParam(value = "group") final List<String> groups) {
-        return processService.getAttachments(process, id, taskId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Comment getComment(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @PathVariable("commentId") final String commentId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups) {
-        return processService.getComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Comment " + commentId + " not found"));
-    }
-
-    @GetMapping(value = "/{id}/$taskName$/{taskId}/comments", produces = MediaType.APPLICATION_JSON_VALUE)
-    public Collection<Comment> getComments(@PathVariable("id") final String id,
-            @PathVariable("taskId") final String taskId,
-            @RequestParam(value = "user", required = false) final String user,
-            @RequestParam(value = "group", required = false) final List<String> groups) {
-        return processService.getComments(process, id, taskId, SecurityPolicy.of(user, groups))
-                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
-    }
-}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java
new file mode 100644
index 0000000000..2bf6dd3bc6
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.myspace.demo;
+
+import java.util.List;
+
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.auth.SecurityPolicy;
+import org.kie.kogito.process.Process;
+import org.kie.kogito.process.ProcessInstance;
+import org.kie.kogito.process.ProcessInstanceReadMode;
+import org.kie.kogito.process.WorkItem;
+import org.kie.kogito.process.impl.Sig;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.usertask.UserTaskService;
+
+import jakarta.inject.Inject;
+
+public class $Type$Resource {
+    
+    @POST
+    @Path("/{id}/$taskName$/trigger")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response signal(@PathParam("id") final String id,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups,
+            @Context UriInfo uriInfo) {
+        return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups))
+                .map(task -> Response
+                        .created(uriInfo.getAbsolutePathBuilder().path(task.getId()).build())
+                        .entity(task.getResults())
+                        .build())
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @POST
+    @Path("/{id}/$taskName$/{taskId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public $Type$Output completeTask(@PathParam("id") final String id,
+            @PathParam("taskId") final String taskId,
+            @QueryParam("phase") @DefaultValue("complete") final String phase,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups,
+            final $TaskOutput$ model) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @PUT
+    @Path("/{id}/$taskName$/{taskId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public $TaskOutput$ saveTask(@PathParam("id") final String id,
+            @PathParam("taskId") final String taskId,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups,
+            final $TaskOutput$ model) {
+        return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap)
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @POST
+    @Path("/{id}/$taskName$/{taskId}/phases/{phase}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public $Type$Output taskTransition(
+            @PathParam("id") final String id,
+            @PathParam("taskId") final String taskId,
+            @PathParam("phase") final String phase,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups,
+            final $TaskOutput$ model) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{id}/$taskName$/{taskId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public $TaskModel$ getWorkItem(@PathParam("id") String id,
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups) {
+        return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from)
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @DELETE
+    @Path("/{id}/$taskName$/{taskId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public $Type$Output abortTask(@PathParam("id") final String id,
+            @PathParam("taskId") final String taskId,
+            @QueryParam("phase") @DefaultValue("abort") final String phase,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null)
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("$taskName$/schema")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Map<String, Object> getSchema() {
+        return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$");
+    }
+
+    @GET
+    @Path("/{id}/$taskName$/{taskId}/schema")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Map<String, Object> getSchemaAndPhases(@PathParam("id") final String id,
+            @PathParam("taskId") final String taskId,
+            @QueryParam("user") final String user,
+            @QueryParam("group") final List<String> groups) {
+        return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups));
+    }
+
+}
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java
new file mode 100644
index 0000000000..7a5e65aea2
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.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 com.myspace.demo;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jbpm.util.JsonSchemaUtil;
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.auth.SecurityPolicy;
+import org.kie.kogito.process.ProcessInstance;
+import org.kie.kogito.process.WorkItem;
+import org.kie.kogito.process.impl.Sig;
+import org.kie.kogito.process.workitem.Comment;
+import org.kie.kogito.process.workitem.TaskMetaInfo;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.usertask.UserTaskService;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class $Type$Resource {
+    
+    @PostMapping(value = "/{id}/$taskName$/trigger", produces = MediaType.APPLICATION_JSON_VALUE,
+            consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity signal(@PathVariable("id") final String id,
+            @RequestParam("user") final String user,
+            @RequestParam("group") final List<String> groups,
+            final UriComponentsBuilder uriComponentsBuilder) {
+
+        return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups))
+                .map(task -> ResponseEntity
+                        .created(uriComponentsBuilder
+                                .path("/$name$/{id}/$taskName$/{taskId}")
+                                .buildAndExpand(id, task.getId()).toUri())
+                        .body(task.getResults()))
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PostMapping(value = "/{id}/$taskName$/{taskId}/phases/{phase}", produces = MediaType.APPLICATION_JSON_VALUE,
+            consumes = MediaType.APPLICATION_JSON_VALUE)
+    public $Type$Output completeTask(@PathVariable("id") final String id,
+            @PathVariable("taskId") final String taskId,
+            @PathVariable("phase") final String phase,
+            @RequestParam("user") final String user,
+            @RequestParam("group") final List<String> groups,
+            @RequestBody(required = false) final $TaskOutput$ model) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PutMapping(value = "/{id}/$taskName$/{taskId}", consumes = MediaType.APPLICATION_JSON_VALUE)
+    public $TaskOutput$ saveTask(@PathVariable("id") final String id,
+            @PathVariable("taskId") final String taskId,
+            @RequestParam(value = "user", required = false) final String user,
+            @RequestParam(value = "group", required = false) final List<String> groups,
+            @RequestBody(required = false) final $TaskOutput$ model) {
+        return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap)
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PostMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE,
+            consumes = MediaType.APPLICATION_JSON_VALUE)
+    public $Type$Output taskTransition(@PathVariable("id") final String id,
+            @PathVariable("taskId") final String taskId,
+            @RequestParam(value = "phase", required = false,
+                    defaultValue = "complete") final String phase,
+            @RequestParam(value = "user",
+                    required = false) final String user,
+            @RequestParam(value = "group",
+                    required = false) final List<String> groups,
+            @RequestBody(required = false) final $TaskOutput$ model) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model)
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public $TaskModel$ getTask(@PathVariable("id") String id,
+            @PathVariable("taskId") String taskId,
+            @RequestParam(value = "user", required = false) final String user,
+            @RequestParam(value = "group",
+                    required = false) final List<String> groups) {
+        return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from)
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @DeleteMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public $Type$Output abortTask(@PathVariable("id") final String id,
+            @PathVariable("taskId") final String taskId,
+            @RequestParam(value = "phase", required = false,
+                    defaultValue = "abort") final String phase,
+            @RequestParam(value = "user", required = false) final String user,
+            @RequestParam(value = "group",
+                    required = false) final List<String> groups) {
+        return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null)
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping(value = "$taskName$/schema", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Map<String, Object> getSchema() {
+        return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$");
+    }
+
+    @GetMapping(value = "/{id}/$taskName$/{taskId}/schema", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Map<String, Object> getSchemaAndPhases(@PathVariable("id") final String id,
+            @PathVariable("taskId") final String taskId,
+            @RequestParam(value = "user", required = false) final String user,
+            @RequestParam(value = "group",
+                    required = false) final List<String> groups) {
+        return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups));
+    }
+
+}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java
new file mode 100644
index 0000000000..231dfcc737
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java
@@ -0,0 +1,248 @@
+/*
+ * 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 com.myspace.demo;
+
+import java.util.Map;
+import java.util.List;
+import java.util.Collection;
+
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.Consumes;
+
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.auth.SecurityPolicy;
+import org.kie.kogito.process.Process;
+import org.kie.kogito.process.ProcessInstance;
+import org.kie.kogito.process.ProcessInstanceReadMode;
+import org.kie.kogito.process.WorkItem;
+import org.kie.kogito.process.impl.Sig;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.usertask.UserTaskService;
+import org.kie.kogito.usertask.view.UserTaskView;
+import org.kie.kogito.usertask.view.UserTaskTransitionView;
+
+import org.kie.kogito.usertask.model.*;
+
+import jakarta.inject.Inject;
+
+@Path("/usertasks/instance")
+public class UserTasksResource {
+
+    @Inject
+    UserTaskService userTaskService;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserTaskView> list(@QueryParam("user") String user, @QueryParam("group") List<String> groups) {
+        return userTaskService.list(IdentityProviders.of(user, groups));
+    }
+
+    @GET
+    @Path("/{taskId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public UserTaskView find(@PathParam("taskId") String taskId, @QueryParam("user") String user, @QueryParam("group") List<String> groups) {
+        return userTaskService.getUserTaskInstance(taskId, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new);
+    }
+
+    @POST
+    @Path("/{taskId}/transition")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public UserTaskView transition(
+            @PathParam("taskId") String taskId,
+            @QueryParam("transitionId") String transitionId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups, Map<String, Object> data) {
+        return userTaskService.transition(taskId, transitionId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{taskId}/transition")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Collection<UserTaskTransitionView> transition(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.allowedTransitions(taskId, IdentityProviders.of(user, groups));
+    }
+
+    @PUT
+    @Path("/{taskId}/outputs")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public UserTaskView setOutput(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            Map<String, Object> data) {
+        return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new);
+    }
+
+    @PUT
+    @Path("/{taskId}/inputs")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public UserTaskView setOutput(@PathParam("id") String id,
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            Map<String, Object> data) {
+        return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{taskId}/comments")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Collection<Comment> getComments(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.getComments(taskId, IdentityProviders.of(user, groups));
+    }
+
+    @POST
+    @Path("/{taskId}/comments")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Comment addComment(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            CommentInfo commentInfo) {
+        Comment comment = new Comment(null, user);
+        comment.setContent(commentInfo.getComment());
+        return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{taskId}/comments/{commentId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Comment getComment(
+            @PathParam("taskId") String taskId,
+            @PathParam("commentId") String commentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.getComment(taskId, commentId, IdentityProviders.of(user, groups))
+                .orElseThrow(() -> new NotFoundException("Comment " + commentId + " not found"));
+    }
+
+    @PUT
+    @Path("/{taskId}/comments/{commentId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Comment updateComment(
+            @PathParam("taskId") String taskId,
+            @PathParam("commentId") String commentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            CommentInfo commentInfo) {
+        Comment comment = new Comment(commentId, user);
+        comment.setContent(commentInfo.getComment());
+        return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups))
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @DELETE
+    @Path("/{taskId}/comments/{commentId}")
+    public Comment deleteComment(
+            @PathParam("taskId") String taskId,
+            @PathParam("commentId") String commentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.removeComment(taskId, commentId, IdentityProviders.of(user, groups))
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{taskId}/attachments")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Collection<Attachment> getAttachments(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.getAttachments(taskId, IdentityProviders.of(user, groups));
+    }
+
+    @POST
+    @Path("/{taskId}/attachments")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Attachment addAttachment(
+            @PathParam("taskId") String taskId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            AttachmentInfo attachmentInfo) {
+        Attachment attachment = new Attachment(null, user);
+        attachment.setName(attachmentInfo.getName());
+        attachment.setContent(attachmentInfo.getUri());
+        return userTaskService.addAttachment(taskId, attachment, IdentityProviders.of(user, groups))
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @PUT
+    @Path("/{taskId}/attachments/{attachmentId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Attachment updateAttachment(
+            @PathParam("taskId") String taskId,
+            @PathParam("attachmentId") String attachmentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups,
+            AttachmentInfo attachmentInfo) {
+        Attachment attachment = new Attachment(attachmentId, user);
+        attachment.setName(attachmentInfo.getName());
+        attachment.setContent(attachmentInfo.getUri());
+        return userTaskService.updateAttachment(taskId, attachment, IdentityProviders.of(user, groups))
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @DELETE
+    @Path("/{taskId}/attachments/{attachmentId}")
+    public Attachment deleteAttachment(
+            @PathParam("taskId") String taskId,
+            @PathParam("attachmentId") String attachmentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.removeAttachment(taskId, attachmentId, IdentityProviders.of(user, groups))
+                .orElseThrow(NotFoundException::new);
+    }
+
+    @GET
+    @Path("/{taskId}/attachments/{attachmentId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Attachment getAttachment(
+            @PathParam("taskId") String taskId,
+            @PathParam("attachmentId") String attachmentId,
+            @QueryParam("user") String user,
+            @QueryParam("group") List<String> groups) {
+        return userTaskService.getAttachment(taskId, attachmentId, IdentityProviders.of(user, groups))
+                .orElseThrow(() -> new NotFoundException("Attachment " + attachmentId + " not found"));
+    }
+
+}
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java
new file mode 100644
index 0000000000..87c757d3d7
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java
@@ -0,0 +1,210 @@
+/*
+ * 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 com.myspace.demo;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+import org.jbpm.util.JsonSchemaUtil;
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.auth.SecurityPolicy;
+import org.kie.kogito.process.ProcessInstance;
+import org.kie.kogito.process.WorkItem;
+import org.kie.kogito.process.impl.Sig;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.usertask.UserTaskService;
+import org.kie.kogito.usertask.view.UserTaskTransitionView;
+import org.kie.kogito.usertask.view.UserTaskView;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.kie.kogito.usertask.model.*;
+
+@RestController
+@RequestMapping(value = "/usertasks/instance", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
+public class UserTasksResource {
+
+    @Autowired
+    UserTaskService userTaskService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public List<UserTaskView> list(@RequestParam("user") String user, @RequestParam("group") List<String> groups) {
+        return userTaskService.list(IdentityProviders.of(user, groups));
+    }
+
+    @GetMapping(value = "/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public UserTaskView find(@PathVariable("taskId") String taskId, @RequestParam("user") String user, @RequestParam("group") List<String> groups) {
+        return userTaskService.getUserTaskInstance(taskId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PostMapping(value = "/{taskId}/transition")
+    public UserTaskView transition(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("transitionId") String transitionId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups, 
+            @RequestBody Map<String, Object> data) {
+        return userTaskService.transition(taskId, transitionId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping(value = "/{taskId}/transition", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Collection<UserTaskTransitionView> transition(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.allowedTransitions(taskId, IdentityProviders.of(user, groups));
+    }
+    
+    @PutMapping("/{taskId}/outputs")
+    public UserTaskView setOutput(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody Map<String, Object> data) {
+        return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PutMapping("/{taskId}/inputs")
+    public UserTaskView setOutput(@PathVariable("id") String id,
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody Map<String, Object> data) {
+        return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping("/{taskId}/comments")
+    public Collection<Comment> getComments(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.getComments(taskId, IdentityProviders.of(user, groups));
+    }
+
+    @PostMapping("/{taskId}/comments")
+    public Comment addComment(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody CommentInfo commentInfo) {
+        Comment comment = new Comment(null, user);
+        comment.setContent(commentInfo.getComment());
+        return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping("/{taskId}/comments/{commentId}")
+    public Comment getComment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("commentId") String commentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.getComment(taskId, commentId, IdentityProviders.of(user, groups))
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Comment " + commentId + " not found"));
+    }
+
+    @PutMapping("/{taskId}/comments/{commentId}")
+    public Comment updateComment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("commentId") String commentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody CommentInfo commentInfo) {
+        Comment comment = new Comment(commentId, user);
+        comment.setContent(commentInfo.getComment());
+        return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @DeleteMapping("/{taskId}/comments/{commentId}")
+    public Comment deleteComment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("commentId") String commentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.removeComment(taskId, commentId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping("/{taskId}/attachments")
+    public Collection<Attachment> getAttachments(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.getAttachments(taskId, IdentityProviders.of(user, groups));
+    }
+
+    @PostMapping("/{taskId}/attachments")
+    public Attachment addAttachment(
+            @PathVariable("taskId") String taskId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody AttachmentInfo attachmentInfo) {
+        Attachment attachment = new Attachment(null, user);
+        attachment.setName(attachmentInfo.getName());
+        attachment.setContent(attachmentInfo.getUri());
+        return userTaskService.addAttachment(taskId, attachment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @PutMapping("/{taskId}/attachments/{attachmentId}")
+    public Attachment updateAttachment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("attachmentId") String attachmentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups,
+            @RequestBody AttachmentInfo attachmentInfo) {
+        Attachment attachment = new Attachment(attachmentId, user);
+        attachment.setName(attachmentInfo.getName());
+        attachment.setContent(attachmentInfo.getUri());
+        return userTaskService.updateAttachment(taskId, attachment, IdentityProviders.of(user, groups))
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @DeleteMapping("/{taskId}/attachments/{attachmentId}")
+    public Attachment deleteAttachment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("attachmentId") String attachmentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.removeAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping("/{taskId}/attachments/{attachmentId}")
+    public Attachment getAttachment(
+            @PathVariable("taskId") String taskId,
+            @PathVariable("attachmentId") String attachmentId,
+            @RequestParam("user") String user,
+            @RequestParam("group") List<String> groups) {
+        return userTaskService.getAttachment(taskId, attachmentId, IdentityProviders.of(user, groups))
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Attachment " + attachmentId + " not found"));
+    }
+
+}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java
index 3486fad384..3d327fb03f 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java
@@ -1,3 +1,4 @@
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -16,6 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import java.util.List;
+
 import org.kie.api.event.process.ProcessEventListener;
 import org.kie.kogito.auth.IdentityProvider;
 import org.kie.kogito.event.EventPublisher;
@@ -27,26 +30,31 @@ import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.uow.events.UnitOfWorkEventListener;
 import org.kie.kogito.usertask.impl.DefaultUserTaskConfig;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
 import org.kie.kogito.usertask.UserTaskEventListenerConfig;
+import org.kie.kogito.usertask.UserTaskInstances;
 
 import jakarta.enterprise.inject.Instance;
 
 @jakarta.inject.Singleton
 public class UserTaskConfig extends DefaultUserTaskConfig {
-    
-    
+
     @jakarta.inject.Inject
     public UserTaskConfig(
             Instance<UserTaskEventListenerConfig> workItemHandlerConfig,
             Instance<UnitOfWorkManager> unitOfWorkManager,
             Instance<JobsService> jobsService,
             Instance<IdentityProvider> identityProvider,
-            Instance<UserTaskLifeCycle> userTaskLifeCycle) {
-        super(workItemHandlerConfig, 
+            Instance<UserTaskLifeCycle> userTaskLifeCycle,
+            Instance<UserTaskAssignmentStrategyConfig> userTaskAssignmentStrategyConfigs,
+            Instance<UserTaskInstances> userTaskInstances) {
+        super(workItemHandlerConfig,
                 unitOfWorkManager,
                 jobsService,
                 identityProvider,
-                userTaskLifeCycle);
+                userTaskLifeCycle,
+                userTaskAssignmentStrategyConfigs,
+                userTaskInstances);
     }
-    
+
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java
index d8e00dbc57..11f7935f43 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java
@@ -1,3 +1,4 @@
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -29,24 +30,30 @@ import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.uow.events.UnitOfWorkEventListener;
 import org.kie.kogito.usertask.impl.DefaultUserTaskConfig;
 import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
 import org.kie.kogito.usertask.UserTaskEventListenerConfig;
+import org.kie.kogito.usertask.UserTaskInstances;
 
 @org.springframework.stereotype.Component
 public class UserTaskConfig extends DefaultUserTaskConfig {
-    
+
     @org.springframework.beans.factory.annotation.Autowired
     public UserTaskConfig(
             List<UserTaskEventListenerConfig> workItemHandlerConfig,
             List<UnitOfWorkManager> unitOfWorkManager,
             List<JobsService> jobsService,
             List<IdentityProvider> identityProvider,
-            List<UserTaskLifeCycle> userTaskLifeCycle) {
+            List<UserTaskLifeCycle> userTaskLifeCycle,
+            List<UserTaskAssignmentStrategyConfig> userTaskAssignmentStrategyConfigs,
+            List<UserTaskInstances> userTaskInstances) {
 
-        super(workItemHandlerConfig, 
+        super(workItemHandlerConfig,
                 unitOfWorkManager,
                 jobsService,
                 identityProvider,
-                userTaskLifeCycle);
+                userTaskLifeCycle,
+                userTaskAssignmentStrategyConfigs,
+                userTaskInstances);
     }
-    
+
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java
index 5e915790f9..3d5213b17a 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java
@@ -22,6 +22,9 @@ import java.util.Set;
 
 import org.kie.kogito.Application;
 import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstances;
 import org.kie.kogito.usertask.impl.DefaultUserTask;
 
 public class UserTaskTemplate extends DefaultUserTask {
@@ -32,9 +35,16 @@ public class UserTaskTemplate extends DefaultUserTask {
     public UserTaskTemplate() {
 
     }
+    
+    @Override
+    public UserTaskAssignmentStrategy getAssignmentStrategy() {
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy();
+    }
 
-    @jakarta.annotation.PostConstruct
-    public void setup() {
-        this.setApplication(application);
+    @Override
+    public UserTaskInstances instances() {
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        return userTaskConfig.userTaskInstances();
     }
 }
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java
index 64e9aa5354..eae587b8ea 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java
@@ -22,6 +22,9 @@ import java.util.Set;
 
 import org.kie.kogito.Application;
 import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstances;
 import org.kie.kogito.usertask.impl.DefaultUserTask;
 
 public class UserTaskTemplate extends DefaultUserTask {
@@ -33,8 +36,16 @@ public class UserTaskTemplate extends DefaultUserTask {
 
     }
 
-    @jakarta.annotation.PostConstruct
-    public void setup() {
-        this.setApplication(application);
+    @Override
+    public UserTaskAssignmentStrategy getAssignmentStrategy() {
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy();
     }
+
+    @Override
+    public UserTaskInstances instances() {
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        return userTaskConfig.userTaskInstances();
+    }
+
 }
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 9d22ea14ee..8e98179784 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
@@ -22,21 +22,40 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.List;
+
+import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTaskInstances;
+
 import jakarta.enterprise.inject.Instance;
 
 @jakarta.enterprise.context.ApplicationScoped
 public class UserTasks implements org.kie.kogito.usertask.UserTasks {
 
+    private static Logger LOG = LoggerFactory.getLogger(UserTasks.class);
+
     @jakarta.inject.Inject
     jakarta.enterprise.inject.Instance<UserTask> userTasks;
 
+    @jakarta.inject.Inject
+    Application application;
+
     private Map<String, UserTask> mappedUserTask = new HashMap<>();
 
     @jakarta.annotation.PostConstruct
     public void setup() {
+        UserTaskInstances userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances();
+        userTaskInstances.setDisconnectUserTaskInstance(this::disconnect);
+        userTaskInstances.setReconnectUserTaskInstance(this::connect);
         for (UserTask userTask : userTasks) {
             mappedUserTask.put(userTask.id(), userTask);
+            LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName());
         }
     }
 
@@ -47,4 +66,31 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
     public Collection<String> userTaskIds() {
         return mappedUserTask.keySet();
     }
+
+    @Override
+    public UserTaskInstances instances() {
+        return application.config().get(UserTaskConfig.class).userTaskInstances();
+    }
+
+    private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        instance.setUserTask(null);
+        instance.setUserTaskEventSupport(null);
+        instance.setUserTaskLifeCycle(null);
+        instance.setInstances(null);
+        return instance;
+    }
+
+    public UserTaskInstance connect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider());
+        userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener);
+        impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager()));
+        instance.setUserTask(application.get(UserTasks.class).userTaskById(instance.getUserTaskId()));
+        instance.setUserTaskEventSupport(impl);
+        instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
+        instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances());
+        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 1e65367bfc..9785b44d7c 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
@@ -22,24 +22,44 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.List;
+
+import org.kie.kogito.Application;
+import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTaskInstances;
 
 @org.springframework.web.context.annotation.ApplicationScope
 @org.springframework.stereotype.Component
 public class UserTasks implements org.kie.kogito.usertask.UserTasks {
-    
+
+    private static Logger LOG = LoggerFactory.getLogger(UserTasks.class);
+
     @org.springframework.beans.factory.annotation.Autowired
     Collection<UserTask> userTasks;
 
+    @org.springframework.beans.factory.annotation.Autowired
+    Application application;
+
     private Map<String, UserTask> mappedUserTask = new HashMap<>();
-    
+
     @jakarta.annotation.PostConstruct
     public void setup() {
+        UserTaskInstances userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances();
+        userTaskInstances.setDisconnectUserTaskInstance(this::disconnect);
+        userTaskInstances.setReconnectUserTaskInstance(this::connect);
+
         for (UserTask userTask : userTasks) {
             mappedUserTask.put(userTask.id(), userTask);
+            LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName());
         }
     }
-    
+
     public UserTask userTaskById(String userTaskId) {
         return mappedUserTask.get(userTaskId);
     }
@@ -47,4 +67,31 @@ public class UserTasks implements org.kie.kogito.usertask.UserTasks {
     public Collection<String> userTaskIds() {
         return mappedUserTask.keySet();
     }
+
+    @Override
+    public UserTaskInstances instances() {
+        return application.config().get(UserTaskConfig.class).userTaskInstances();
+    }
+
+    private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        instance.setUserTask(null);
+        instance.setUserTaskEventSupport(null);
+        instance.setUserTaskLifeCycle(null);
+        instance.setInstances(null);
+        return instance;
+    }
+
+    public UserTaskInstance connect(UserTaskInstance userTaskInstance) {
+        DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance;
+        UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class);
+        KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider());
+        userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener);
+        impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager()));
+        instance.setUserTask(application.get(org.kie.kogito.usertask.UserTasks.class).userTaskById(instance.getUserTaskId()));
+        instance.setUserTaskEventSupport(impl);
+        instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle());
+        instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances());
+        return instance;
+    }
 }
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java
new file mode 100644
index 0000000000..837b7312e1
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java
@@ -0,0 +1,68 @@
+/*
+ * 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 $Package$;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.enterprise.inject.Instance;
+
+import org.kie.kogito.Application;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener;
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.usertask.UserTask;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
+import org.kie.kogito.usertask.UserTaskEventListener;
+import org.kie.kogito.usertask.UserTaskService;
+import org.kie.kogito.usertask.UserTaskConfig;
+import org.kie.kogito.usertask.UserTaskEventListenerConfig;
+import org.kie.kogito.usertask.impl.DefaultUserTaskEventListenerConfig;
+import org.kie.kogito.usertask.impl.DefaultUserTaskAssignmentStrategyConfig;
+import org.kie.kogito.usertask.impl.UserTaskServiceImpl;
+
+@ApplicationScoped
+public class UserTasksServiceProducer {
+
+    @Produces
+    public UserTaskService userTaskService(Application application) {
+        return new UserTaskServiceImpl(application);
+    }
+
+    @Produces
+    public UserTaskEventListener userTaskListener(Processes processes) {
+        return new UserTaskKogitoWorkItemHandlerProcessListener(processes);
+    }
+
+    @Produces
+    public KogitoWorkItemHandler userTaskWorkItemHandler(Application application) {
+        return new UserTaskKogitoWorkItemHandler(application);
+    }
+
+    @Produces 
+    public UserTaskEventListenerConfig userTaskEventListenerConfig(Instance<UserTaskEventListener> userTaskEventListener) {
+        return new DefaultUserTaskEventListenerConfig(userTaskEventListener);
+    }
+    
+    @Produces 
+    public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig(Instance<UserTaskAssignmentStrategy> userTaskAssignmentStrategy) {
+        return new DefaultUserTaskAssignmentStrategyConfig(userTaskAssignmentStrategy);
+    }
+}
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java
new file mode 100644
index 0000000000..088d7ad193
--- /dev/null
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java
@@ -0,0 +1,67 @@
+/*
+ * 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 $Package$;
+
+import java.util.List;
+import org.kie.kogito.Application;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler;
+import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener;
+import org.kie.kogito.process.ProcessService;
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.process.impl.ProcessServiceImpl;
+import org.kie.kogito.usertask.UserTaskEventListenerConfig;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig;
+import org.kie.kogito.usertask.UserTaskAssignmentStrategy;
+import org.kie.kogito.usertask.UserTaskEventListener;
+import org.kie.kogito.usertask.impl.DefaultUserTaskEventListenerConfig;
+import org.kie.kogito.usertask.impl.DefaultUserTaskAssignmentStrategyConfig;
+import org.kie.kogito.usertask.impl.UserTaskServiceImpl;
+import org.kie.kogito.usertask.UserTaskService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class UserTaskServiceProducer {
+
+    @Bean
+    public UserTaskService userTaskService(Application application) {
+        return new UserTaskServiceImpl(application);
+    }
+
+    @Bean
+    public UserTaskEventListener userTaskListener(Processes processes) {
+        return new UserTaskKogitoWorkItemHandlerProcessListener(processes);
+    }
+
+    @Bean
+    public KogitoWorkItemHandler userTaskWorkItemHandler(Application application) {
+        return new UserTaskKogitoWorkItemHandler(application);
+    }
+
+    @Bean 
+    public UserTaskEventListenerConfig userTaskEventListenerConfig(List<UserTaskEventListener> userTaskEventListener) {
+        return new DefaultUserTaskEventListenerConfig(userTaskEventListener);
+    }
+    
+    @Bean 
+    public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig(List<UserTaskAssignmentStrategy> userTaskAssignmentStrategy) {
+        return new DefaultUserTaskAssignmentStrategyConfig(userTaskAssignmentStrategy);
+    }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
index 72a8a4a6fc..c0d75bce39 100644
--- a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
+++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
@@ -303,7 +303,7 @@ public abstract class PersistenceTest {
                 .queryParam("user", "user")
                 .queryParam("group", "agroup")
                 .when()
-                .post("/AdHocProcess/{pId}/CloseTask")
+                .post("/AdHocProcess/{pId}/CloseTask/trigger")
                 .then()
                 .log().everything()
                 .statusCode(201)
diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2
new file mode 100644
index 0000000000..6d8f8618cd
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:bpsim="http://www.bpsim.org/schemas/1.0" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:drools="http://www.jboss.org/drools" id="_ENJmkGP9ED2iIdGE-n8pYg" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd http://www. [...]
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_PriorityInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_CommentInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_DescriptionInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_CreatedByInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_GroupIdInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_ContentInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_NotStartedReassignInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_NotCompletedReassignInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_NotStartedNotifyInputXItem" structureRef="Object"/>
+  <bpmn2:itemDefinition id="__74EA6719-7F99-48CD-A98E-17D422668B87_NotCompletedNotifyInputXItem" structureRef="Object"/>
+  <bpmn2:signal id="_-567202649" name="continue"/>
+  <bpmn2:collaboration id="_4DD91E78-A982-46F9-A427-611946D40403" name="Default Collaboration">
+    <bpmn2:participant id="_239FEF97-E424-41F3-B3E9-D626D7EC319D" name="Pool Participant" processRef="addedtask"/>
+  </bpmn2:collaboration>
+  <bpmn2:process id="AddedTask" drools:packageName="com.example" drools:version="1.0" drools:adHoc="false" name="addedtask" isExecutable="true" processType="Public">
+    <bpmn2:sequenceFlow id="_D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F" sourceRef="_74EA6719-7F99-48CD-A98E-17D422668B87" targetRef="_1D65864A-96AF-44FC-92CE-2073B9FBA7D0">
+      <bpmn2:extensionElements>
+        <drools:metaData name="isAutoConnection.target">
+          <drools:metaValue><![CDATA[true]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_FA3E1FF8-4894-47DA-90EB-DAB27BD7E925" sourceRef="_7C0F340A-57A6-40ED-A802-4C8A5B61B8E4" targetRef="_8F029B9C-9B3C-4907-8C68-69B30FD85A88">
+      <bpmn2:extensionElements>
+        <drools:metaData name="isAutoConnection.target">
+          <drools:metaValue><![CDATA[true]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_2DFFEEA8-CA22-4076-A703-DB36A6471775" sourceRef="_1D65864A-96AF-44FC-92CE-2073B9FBA7D0" targetRef="_7C0F340A-57A6-40ED-A802-4C8A5B61B8E4">
+      <bpmn2:extensionElements>
+        <drools:metaData name="isAutoConnection.target">
+          <drools:metaValue><![CDATA[true]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_916690AE-8B09-4FA5-BF0D-8638E01A7472" sourceRef="_9660EA72-BFEC-4FAE-B5C3-48513A420288" targetRef="_74EA6719-7F99-48CD-A98E-17D422668B87"/>
+    <bpmn2:sequenceFlow id="_C7A1A566-057F-4A80-B654-61270F82F365" sourceRef="_D7D85E86-FA30-43CB-9DB6-A3FC067049C1" targetRef="_9660EA72-BFEC-4FAE-B5C3-48513A420288"/>
+    <bpmn2:intermediateCatchEvent id="_1D65864A-96AF-44FC-92CE-2073B9FBA7D0">
+      <bpmn2:incoming>_D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F</bpmn2:incoming>
+      <bpmn2:outgoing>_2DFFEEA8-CA22-4076-A703-DB36A6471775</bpmn2:outgoing>
+      <bpmn2:signalEventDefinition signalRef="_-567202649"/>
+    </bpmn2:intermediateCatchEvent>
+    <bpmn2:userTask id="_74EA6719-7F99-48CD-A98E-17D422668B87" name="HT">
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[HT]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_916690AE-8B09-4FA5-BF0D-8638E01A7472</bpmn2:incoming>
+      <bpmn2:outgoing>_D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F</bpmn2:outgoing>
+      <bpmn2:ioSpecification>
+        <bpmn2:dataInput id="_74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX" drools:dtype="Object" itemSubjectRef="__74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputXItem" name="TaskName"/>
+        <bpmn2:dataInput id="_74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX" drools:dtype="Object" itemSubjectRef="__74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputXItem" name="Skippable"/>
+        <bpmn2:inputSet>
+          <bpmn2:dataInputRefs>_74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX</bpmn2:dataInputRefs>
+          <bpmn2:dataInputRefs>_74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX</bpmn2:dataInputRefs>
+        </bpmn2:inputSet>
+      </bpmn2:ioSpecification>
+      <bpmn2:dataInputAssociation>
+        <bpmn2:targetRef>_74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX</bpmn2:targetRef>
+        <bpmn2:assignment>
+          <bpmn2:from xsi:type="bpmn2:tFormalExpression"><![CDATA[Task]]></bpmn2:from>
+          <bpmn2:to xsi:type="bpmn2:tFormalExpression"><![CDATA[_74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX]]></bpmn2:to>
+        </bpmn2:assignment>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:dataInputAssociation>
+        <bpmn2:targetRef>_74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX</bpmn2:targetRef>
+        <bpmn2:assignment>
+          <bpmn2:from xsi:type="bpmn2:tFormalExpression"><![CDATA[false]]></bpmn2:from>
+          <bpmn2:to xsi:type="bpmn2:tFormalExpression"><![CDATA[_74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX]]></bpmn2:to>
+        </bpmn2:assignment>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:potentialOwner id="_ENPGIGP9ED2iIdGE-n8pYg">
+        <bpmn2:resourceAssignmentExpression id="_ENPGIWP9ED2iIdGE-n8pYg">
+          <bpmn2:formalExpression>mary</bpmn2:formalExpression>
+        </bpmn2:resourceAssignmentExpression>
+      </bpmn2:potentialOwner>
+    </bpmn2:userTask>
+    <bpmn2:endEvent id="_8F029B9C-9B3C-4907-8C68-69B30FD85A88">
+      <bpmn2:incoming>_FA3E1FF8-4894-47DA-90EB-DAB27BD7E925</bpmn2:incoming>
+    </bpmn2:endEvent>
+    <bpmn2:scriptTask id="_7C0F340A-57A6-40ED-A802-4C8A5B61B8E4" name="log process continuation" scriptFormat="http://www.java.com/java">
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[log process continuation]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_2DFFEEA8-CA22-4076-A703-DB36A6471775</bpmn2:incoming>
+      <bpmn2:outgoing>_FA3E1FF8-4894-47DA-90EB-DAB27BD7E925</bpmn2:outgoing>
+      <bpmn2:script>System.out.println("Ending process " + kcontext.getProcessInstance().getId() + " [" + kcontext.getProcessInstance().getProcessName() + ", v" + kcontext.getProcessInstance().getProcessVersion() + "]");</bpmn2:script>
+    </bpmn2:scriptTask>
+    <bpmn2:scriptTask id="_9660EA72-BFEC-4FAE-B5C3-48513A420288" name="log process creation" scriptFormat="http://www.java.com/java">
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[log process creation]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_C7A1A566-057F-4A80-B654-61270F82F365</bpmn2:incoming>
+      <bpmn2:outgoing>_916690AE-8B09-4FA5-BF0D-8638E01A7472</bpmn2:outgoing>
+      <bpmn2:script>System.out.println("Started process " + kcontext.getProcessInstance().getId() + " [" + kcontext.getProcessInstance().getProcessName() + ", v" + kcontext.getProcessInstance().getProcessVersion() + "]");</bpmn2:script>
+    </bpmn2:scriptTask>
+    <bpmn2:startEvent id="_D7D85E86-FA30-43CB-9DB6-A3FC067049C1">
+      <bpmn2:outgoing>_C7A1A566-057F-4A80-B654-61270F82F365</bpmn2:outgoing>
+    </bpmn2:startEvent>
+  </bpmn2:process>
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="addedtask">
+      <bpmndi:BPMNShape id="shape__D7D85E86-FA30-43CB-9DB6-A3FC067049C1" bpmnElement="_D7D85E86-FA30-43CB-9DB6-A3FC067049C1">
+        <dc:Bounds height="56" width="56" x="112" y="261"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__9660EA72-BFEC-4FAE-B5C3-48513A420288" bpmnElement="_9660EA72-BFEC-4FAE-B5C3-48513A420288">
+        <dc:Bounds height="102" width="154" x="248" y="238"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__7C0F340A-57A6-40ED-A802-4C8A5B61B8E4" bpmnElement="_7C0F340A-57A6-40ED-A802-4C8A5B61B8E4">
+        <dc:Bounds height="102" width="154" x="838" y="238"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__8F029B9C-9B3C-4907-8C68-69B30FD85A88" bpmnElement="_8F029B9C-9B3C-4907-8C68-69B30FD85A88">
+        <dc:Bounds height="56" width="56" x="1050" y="261"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__74EA6719-7F99-48CD-A98E-17D422668B87" bpmnElement="_74EA6719-7F99-48CD-A98E-17D422668B87">
+        <dc:Bounds height="102" width="154" x="462" y="238"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__1D65864A-96AF-44FC-92CE-2073B9FBA7D0" bpmnElement="_1D65864A-96AF-44FC-92CE-2073B9FBA7D0">
+        <dc:Bounds height="56" width="56" x="681" y="261"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge id="edge_shape__D7D85E86-FA30-43CB-9DB6-A3FC067049C1_to_shape__9660EA72-BFEC-4FAE-B5C3-48513A420288" bpmnElement="_C7A1A566-057F-4A80-B654-61270F82F365">
+        <di:waypoint x="140" y="289"/>
+        <di:waypoint x="325" y="289"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="edge_shape__9660EA72-BFEC-4FAE-B5C3-48513A420288_to_shape__74EA6719-7F99-48CD-A98E-17D422668B87" bpmnElement="_916690AE-8B09-4FA5-BF0D-8638E01A7472">
+        <di:waypoint x="325" y="289"/>
+        <di:waypoint x="539" y="289"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="edge_shape__1D65864A-96AF-44FC-92CE-2073B9FBA7D0_to_shape__7C0F340A-57A6-40ED-A802-4C8A5B61B8E4" bpmnElement="_2DFFEEA8-CA22-4076-A703-DB36A6471775">
+        <di:waypoint x="709" y="289"/>
+        <di:waypoint x="838" y="289"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="edge_shape__7C0F340A-57A6-40ED-A802-4C8A5B61B8E4_to_shape__8F029B9C-9B3C-4907-8C68-69B30FD85A88" bpmnElement="_FA3E1FF8-4894-47DA-90EB-DAB27BD7E925">
+        <di:waypoint x="915" y="289"/>
+        <di:waypoint x="1127" y="261"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="edge_shape__74EA6719-7F99-48CD-A98E-17D422668B87_to_shape__1D65864A-96AF-44FC-92CE-2073B9FBA7D0" bpmnElement="_D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F">
+        <di:waypoint x="539" y="289"/>
+        <di:waypoint x="681" y="289"/>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+  <bpmn2:relationship type="BPSimData">
+    <bpmn2:extensionElements>
+      <bpsim:BPSimData>
+        <bpsim:Scenario id="default" name="Simulationscenario">
+          <bpsim:ScenarioParameters/>
+          <bpsim:ElementParameters elementRef="_D7D85E86-FA30-43CB-9DB6-A3FC067049C1">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters elementRef="_9660EA72-BFEC-4FAE-B5C3-48513A420288">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters elementRef="_7C0F340A-57A6-40ED-A802-4C8A5B61B8E4">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters elementRef="_74EA6719-7F99-48CD-A98E-17D422668B87">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+        </bpsim:Scenario>
+      </bpsim:BPSimData>
+    </bpmn2:extensionElements>
+    <bpmn2:source>_ENJmkGP9ED2iIdGE-n8pYg</bpmn2:source>
+    <bpmn2:target>_ENJmkGP9ED2iIdGE-n8pYg</bpmn2:target>
+  </bpmn2:relationship>
+</bpmn2:definitions>
\ No newline at end of file
diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java
index fd779ca575..cc668b0643 100644
--- a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java
+++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java
@@ -18,9 +18,84 @@
  */
 package org.kie.kogito.it;
 
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
 import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
 
 @QuarkusIntegrationTest
 class InfinispanPersistenceIT extends PersistenceTest {
+    private static final String USER_TASK_BASE_PATH = "/usertasks/instance";
+
+    @Test
+    public void testStartApprovalAuthorized() {
+        // start new approval
+        String id = given()
+                .body("{}")
+                .contentType(ContentType.JSON)
+                .when()
+                .post("/AddedTask")
+                .then()
+                .statusCode(201)
+                .body("id", notNullValue()).extract().path("id");
+        // get all active approvals
+        given()
+                .accept(ContentType.JSON)
+                .when()
+                .get("/AddedTask")
+                .then()
+                .statusCode(200)
+                .body("size()", is(1), "[0].id", is(id));
+
+        // get just started approval
+        given()
+                .accept(ContentType.JSON)
+                .when()
+                .get("/AddedTask/" + id)
+                .then()
+                .statusCode(200)
+                .body("id", is(id));
+
+        // tasks assigned in just started approval
+
+        String userTaskId = given()
+                .basePath(USER_TASK_BASE_PATH)
+                .queryParam("user", "mary")
+                .queryParam("group", "managers")
+                .contentType(ContentType.JSON)
+                .when()
+                .get()
+                .then()
+                .statusCode(200)
+                .extract()
+                .body()
+                .path("[0].id");
+
+        given()
+                .contentType(ContentType.JSON)
+                .basePath(USER_TASK_BASE_PATH)
+                .queryParam("transitionId", "complete")
+                .queryParam("user", "mary")
+                .queryParam("group", "managers")
+                .body(Collections.emptyMap())
+                .when()
+                .post("/{userTaskId}/transition", userTaskId)
+                .then()
+                .statusCode(200);
 
+        // get all active approvals
+        given()
+                .accept(ContentType.JSON)
+                .when()
+                .get("/AddedTask")
+                .then()
+                .statusCode(200)
+                .body("size()", is(1));
+    }
 }
diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java
index 083b39bec0..e0007fe853 100644
--- a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java
+++ b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java
@@ -64,7 +64,7 @@ class AdHocFragmentsIT {
                 .queryParam("user", "john")
                 .queryParam("group", "manager")
                 .when()
-                .post("/AdHocFragments/{pid}/AdHocTask1", id)
+                .post("/AdHocFragments/{pid}/AdHocTask1/trigger", id)
                 .then()
                 .statusCode(201)
                 .header("Location", not(emptyOrNullString()))
diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java
index d5a5513a16..17cac753e5 100644
--- a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java
+++ b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java
@@ -20,22 +20,21 @@ package org.kie.kogito.integrationtests.quarkus;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.acme.travels.Traveller;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.kie.kogito.task.management.service.TaskInfo;
 import org.kie.kogito.usertask.model.AttachmentInfo;
+import org.kie.kogito.usertask.model.CommentInfo;
 
 import io.quarkus.test.junit.QuarkusIntegrationTest;
 import io.restassured.RestAssured;
 import io.restassured.http.ContentType;
 
-import jakarta.ws.rs.core.UriBuilder;
-
 import static io.restassured.RestAssured.given;
 import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchema;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@@ -160,11 +159,10 @@ class TaskIT {
     }
 
     @Test
-    @Disabled("Revisited after ht endpoints")
     void testCommentAndAttachment() {
-        Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish");
+        Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null);
 
-        String processId = given()
+        given()
                 .contentType(ContentType.JSON)
                 .when()
                 .body(Collections.singletonMap("traveller", traveller))
@@ -176,154 +174,131 @@ class TaskIT {
 
         String taskId = given()
                 .contentType(ContentType.JSON)
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .when()
-                .get("/approvals/{processId}/tasks")
+                .get("/usertasks/instance")
                 .then()
                 .statusCode(200)
                 .extract()
                 .path("[0].id");
 
-        final String commentId = given().contentType(ContentType.TEXT)
+        final String commentId = given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
-                .body("We need to act")
-                .post("/approvals/{processId}/firstLineApproval/{taskId}/comments")
+                .body(new CommentInfo("We need to act"))
+                .post("/usertasks/instance/{taskId}/comments")
                 .then()
-                .statusCode(201)
+                .statusCode(200)
                 .extract()
                 .path("id");
 
         final String commentText = "We have done everything we can";
-        given().contentType(ContentType.TEXT)
+        given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .body(commentText)
-                .put("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .body(new CommentInfo(commentText))
+                .put("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200);
 
         assertEquals(commentText, given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .get("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200).extract().path("content"));
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .delete("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200);
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
-                .queryParam("group", "managers")
-                .pathParam("processId", processId)
-                .pathParam("taskId", taskId)
-                .pathParam("commentId", commentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
-                .then()
-                .statusCode(404);
-
-        given().contentType(ContentType.JSON)
-                .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .get("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(404);
 
         final String attachmentId = given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
-                .body(new AttachmentInfo(UriBuilder.fromPath("pepito.txt").scheme("file").build(), "pepito.txt"))
-                .post("/approvals/{processId}/firstLineApproval/{taskId}/attachments")
+                .body(new AttachmentInfo(URI.create("pepito.txt"), "pepito.txt"))
+                .post("/usertasks/instance/{taskId}/attachments")
                 .then()
-                .statusCode(201)
+                .statusCode(200)
                 .extract()
                 .path("id");
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .body(new AttachmentInfo(UriBuilder.fromPath("/home/fulanito.txt").scheme("file").build()))
-                .put("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .body(new AttachmentInfo(URI.create("file:/home/fulanito.txt")))
+                .put("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200);
 
         given().contentType(
                 ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .get("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200).body("name", equalTo("fulanito.txt")).body("content", equalTo(
                         "file:/home/fulanito.txt"));
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200);
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .get("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(404);
 
         given().contentType(ContentType.JSON)
                 .when()
-                .queryParam("user", "manager")
+                .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(404);
     }
diff --git a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java
index cec3f11a37..0fb04479a5 100644
--- a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java
+++ b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java
@@ -58,7 +58,7 @@ class AdHocFragmentsTest extends BaseRestTest {
                 .queryParam("user", "john")
                 .queryParam("group", "manager")
                 .when()
-                .post("/AdHocFragments/{pid}/AdHocTask1", id)
+                .post("/AdHocFragments/{pid}/AdHocTask1/trigger", id)
                 .then()
                 .statusCode(201)
                 .header("Location", not(emptyOrNullString()))
diff --git a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
index 970968e649..5da1e8e6cd 100644
--- a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
+++ b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
@@ -31,11 +31,11 @@ import java.util.stream.Stream;
 import org.acme.travels.Address;
 import org.acme.travels.Traveller;
 import org.jbpm.util.JsonSchemaUtil;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.kie.kogito.task.management.service.TaskInfo;
 import org.kie.kogito.usertask.model.AttachmentInfo;
+import org.kie.kogito.usertask.model.CommentInfo;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
@@ -98,7 +98,7 @@ public class TaskTest extends BaseRestTest {
 
     @Test
     void testJsonSchemaFiles() {
-        long expectedJsonSchemas = 23;
+        long expectedJsonSchemas = 25;
         Path jsonDir = Paths.get("target", "classes").resolve(JsonSchemaUtil.getJsonDir());
         try (Stream<Path> paths = Files.walk(jsonDir)) {
             long generatedJsonSchemas = paths
@@ -111,11 +111,10 @@ public class TaskTest extends BaseRestTest {
     }
 
     @Test
-    @Disabled("Revisited after ht endpoints")
     void testCommentAndAttachment() {
         Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null);
 
-        String processId = given()
+        given()
                 .contentType(ContentType.JSON)
                 .when()
                 .body(Collections.singletonMap("traveller", traveller))
@@ -129,37 +128,34 @@ public class TaskTest extends BaseRestTest {
                 .contentType(ContentType.JSON)
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .when()
-                .get("/approvals/{processId}/tasks")
+                .get("/usertasks/instance")
                 .then()
                 .statusCode(200)
                 .extract()
                 .path("[0].id");
 
-        final String commentId = given().contentType(ContentType.TEXT)
+        final String commentId = given().contentType(ContentType.JSON)
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
-                .body("We need to act")
-                .post("/approvals/{processId}/firstLineApproval/{taskId}/comments")
+                .body(new CommentInfo("We need to act"))
+                .post("/usertasks/instance/{taskId}/comments")
                 .then()
-                .statusCode(201)
+                .statusCode(200)
                 .extract()
                 .path("id");
 
         final String commentText = "We have done everything we can";
-        given().contentType(ContentType.TEXT)
+        given().contentType(ContentType.JSON)
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .body(commentText)
-                .put("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .body(new CommentInfo(commentText))
+                .put("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200);
 
@@ -167,10 +163,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .get("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200).extract().path("content"));
 
@@ -178,10 +173,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .delete("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(200);
 
@@ -189,10 +183,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("commentId", commentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}")
+                .get("/usertasks/instance/{taskId}/comments/{commentId}")
                 .then()
                 .statusCode(404);
 
@@ -200,12 +193,11 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .body(new AttachmentInfo(URI.create("pepito.txt"), "pepito.txt"))
-                .post("/approvals/{processId}/firstLineApproval/{taskId}/attachments")
+                .post("/usertasks/instance/{taskId}/attachments")
                 .then()
-                .statusCode(201)
+                .statusCode(200)
                 .extract()
                 .path("id");
 
@@ -213,11 +205,10 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
                 .body(new AttachmentInfo(URI.create("file:/home/fulanito.txt")))
-                .put("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .put("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200);
 
@@ -226,10 +217,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .get("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200).body("name", equalTo("fulanito.txt")).body("content", equalTo(
                         "file:/home/fulanito.txt"));
@@ -238,10 +228,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(200);
 
@@ -249,10 +238,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .get("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(404);
 
@@ -260,10 +248,9 @@ public class TaskTest extends BaseRestTest {
                 .when()
                 .queryParam("user", "admin")
                 .queryParam("group", "managers")
-                .pathParam("processId", processId)
                 .pathParam("taskId", taskId)
                 .pathParam("attachmentId", attachmentId)
-                .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}")
+                .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}")
                 .then()
                 .statusCode(404);
     }
diff --git a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
index 7ef8b40a8e..15c79f08f9 100644
--- a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
+++ b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java
@@ -46,6 +46,7 @@ import static org.awaitility.Awaitility.await;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.emptyOrNullString;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -303,19 +304,31 @@ public abstract class PersistenceTest {
                 .extract()
                 .path("id");
 
-        given().contentType(ContentType.JSON)
+        String location = given().contentType(ContentType.JSON)
                 .pathParam("pId", pId)
+                .queryParam("user", "user")
+                .queryParam("group", "agroup")
                 .when()
-                .get("/AdHocProcess/{pId}")
+                .post("/AdHocProcess/{pId}/CloseTask/trigger")
                 .then()
-                .statusCode(200);
+                .log().everything()
+                .statusCode(201)
+                .header("Location", notNullValue())
+                .extract()
+                .header("Location");
 
-        given().contentType(ContentType.JSON)
-                .pathParam("pId", pId)
+        String taskId = location.substring(location.lastIndexOf("/") + 1);
+
+        given()
+                .queryParam("user", "user")
+                .queryParam("group", "agroup")
+                .contentType(ContentType.JSON)
                 .when()
-                .delete("/AdHocProcess/{pId}")
+                .body(Map.of("status", "closed"))
+                .post("/AdHocProcess/{pId}/CloseTask/{taskId}", pId, taskId)
                 .then()
-                .statusCode(200);
+                .statusCode(200)
+                .body("status", equalTo("closed"));
 
         given().contentType(ContentType.JSON)
                 .pathParam("pId", pId)


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