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

porcelli 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 912c47011d [incubator-kie-issues-1550] Add Transaction error handling 
when transaction is enabled (#3740)
912c47011d is described below

commit 912c47011d8ff244e4d578795de76bf57e9c2b6c
Author: Enrique <[email protected]>
AuthorDate: Fri Nov 1 20:02:34 2024 +0100

    [incubator-kie-issues-1550] Add Transaction error handling when transaction 
is enabled (#3740)
    
    * [incubator-kie-issues-1550] Add Transaction error handling when 
transaction is enabled
    
    * ProcessGenerationIT fix tests
    
    * clean up exception handling
    
    * add exception handler
    
    * handlers creation for spring boot and quarkus
    
    * fix throwable
    
    * fix compilation errors
    
    * fix ProcessGenerationIT
    
    * fix java standalone no tx
    
    * fix quarkus exceptions handling
    
    * ProcessResourceGeneratorTest fix
    
    * fix resources
    
    * fix quarkus error handling
    
    * fix spring boot unitof work
    
    * fix transactions templates
    
    * fix template class name
    
    * fix
    
    * fix composed messages based on the exceptions processed
---
 .../exceptions/AbstractExceptionsHandler.java      | 142 ++++++++++++++
 .../resource/exceptions/BaseExceptionsHandler.java | 203 ---------------------
 .../resource/exceptions/ExceptionBodyMessage.java  |  57 ++++++
 .../exceptions/ExceptionBodyMessageFunctions.java  | 124 +++++++++++++
 .../resource/exceptions/RestExceptionHandler.java  |  58 ++++++
 .../exceptions/BaseExceptionHandlerTest.java       |  14 +-
 .../org/kie/kogito/handler/ExceptionHandler.java   |  12 +-
 .../java/org/jbpm/bpmn2/xml/ProcessHandler.java    |   4 +-
 .../java/org/jbpm/workflow/core/impl/NodeImpl.java |   6 +-
 .../org/jbpm/workflow/core/node/ActionNode.java    |   6 +-
 .../java/org/jbpm/workflow/core/node/EndNode.java  |   4 +-
 .../org/jbpm/workflow/core/node/EventNode.java     |   6 +-
 .../org/jbpm/workflow/core/node/MilestoneNode.java |   6 +-
 .../org/jbpm/workflow/core/node/RuleSetNode.java   |   5 +-
 .../java/org/jbpm/workflow/core/node/Split.java    |   4 +-
 .../org/jbpm/workflow/core/node/StartNode.java     |   4 +-
 .../jbpm/workflow/core/node/SubProcessNode.java    |   6 +-
 .../org/jbpm/workflow/core/node/TimerNode.java     |   6 +-
 .../org/jbpm/workflow/core/node/WorkItemNode.java  |   6 +-
 .../instance/WorkflowProcessParameters.java        |  56 ++++++
 .../workflow/instance/impl/NodeInstanceImpl.java   |  18 +-
 .../instance/impl/WorkflowProcessInstanceImpl.java |  10 +
 .../instance/node/ForEachNodeInstance.java         |   4 +-
 .../codegen/process/ProcessGenerationIT.java       |  16 +-
 .../kie/kogito/codegen/process/ProcessCodegen.java |  29 ++-
 .../kogito/codegen/process/util/CodegenUtil.java   |  12 +-
 ...ExceptionHandlerTransactionQuarkusTemplate.java |  77 ++++++++
 .../ExceptionHandlerTransactionSpringTemplate.java |  73 ++++++++
 quarkus/addons/rest-exception-handler/pom.xml      |   4 +
 .../resource/exceptions/BaseExceptionMapper.java   |   7 -
 .../resource/exceptions/ExceptionsHandler.java     |  28 +--
 ...nMapper.java => ExceptionsHandlerProducer.java} |  15 +-
 .../exceptions/IllegalArgumentExceptionMapper.java |   4 +
 .../InvalidLifeCyclePhaseExceptionMapper.java      |   4 +
 .../InvalidTransitionExceptionMapper.java          |   4 +
 .../NodeInstanceNotFoundExceptionMapper.java       |   4 +
 .../exceptions/NodeNotFoundExceptionMapper.java    |   4 +
 .../exceptions/NotAuthorizedExceptionMapper.java   |   4 +
 .../ProcessInstanceDuplicatedExceptionMapper.java  |   4 +
 .../ProcessInstanceExecutionExceptionMapper.java   |   4 +
 .../ProcessInstanceNotFoundExceptionMapper.java    |   4 +
 ...erTaskInstanceNotAuthorizedExceptionMapper.java |   4 +
 .../UserTaskInstanceNotFoundExceptionMapper.java   |   4 +
 .../UserTaskTransitionExceptionMapper.java         |   4 +
 .../VariableViolationExceptionMapper.java          |   4 +
 .../WorkItemExecutionExceptionMapper.java          |   4 +
 .../WorkItemNotFoundExceptionMapper.java           |   4 +
 .../resource/exceptions/ExceptionsHandlerTest.java |   8 +-
 .../addon/source/files/SourceFilesResource.java    |  47 +++--
 .../source/files/SourceFilesResourceTest.java      |   4 +-
 .../exceptions/springboot/ExceptionsHandler.java   |  59 +++---
 .../springboot/ExceptionsHandlerTest.java          |  22 ++-
 .../org/kie/kogito/process/KogitoBeanProducer.java |   9 +
 53 files changed, 887 insertions(+), 344 deletions(-)

diff --git 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java
 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java
new file mode 100644
index 0000000000..26923edcd2
--- /dev/null
+++ 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java
@@ -0,0 +1,142 @@
+/*
+ * 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.resource.exceptions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.kie.kogito.handler.ExceptionHandler;
+import org.kie.kogito.internal.process.runtime.MessageException;
+import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException;
+import org.kie.kogito.internal.process.workitem.InvalidTransitionException;
+import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
+import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
+import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException;
+import org.kie.kogito.process.NodeInstanceNotFoundException;
+import org.kie.kogito.process.NodeNotFoundException;
+import org.kie.kogito.process.ProcessInstanceDuplicatedException;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
+import org.kie.kogito.process.ProcessInstanceNotFoundException;
+import org.kie.kogito.process.VariableViolationException;
+import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException;
+import org.kie.kogito.usertask.UserTaskInstanceNotFoundException;
+import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.nodeInstanceNotFoundMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.nodeNotFoundMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceDuplicatedMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceExecutionMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceNotFoundExceptionMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.variableViolationMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.workItemExecutionMessageException;
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.workItemNotFoundMessageException;
+import static 
org.kie.kogito.resource.exceptions.RestExceptionHandler.newExceptionHandler;
+
+public abstract class AbstractExceptionsHandler<T> {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractExceptionsHandler.class);
+
+    RestExceptionHandler<? extends Exception, T> DEFAULT_HANDLER = 
newExceptionHandler(Exception.class, this::badRequest);
+
+    private Map<Class<? extends Throwable>, RestExceptionHandler<? extends 
Throwable, T>> mapper;
+
+    private List<ExceptionHandler> errorHandlers;
+
+    protected AbstractExceptionsHandler() {
+        this(Collections.emptyList());
+    }
+
+    protected AbstractExceptionsHandler(Iterable<ExceptionHandler> 
errorHandlers) {
+        List<RestExceptionHandler<? extends Throwable, T>> handlers = 
List.<RestExceptionHandler<? extends Throwable, T>> of(
+                newExceptionHandler(InvalidLifeCyclePhaseException.class, 
this::badRequest),
+                newExceptionHandler(UserTaskTransitionException.class, 
this::badRequest),
+                newExceptionHandler(UserTaskInstanceNotFoundException.class, 
this::notFound),
+                
newExceptionHandler(UserTaskInstanceNotAuthorizedException.class, 
this::forbidden),
+                newExceptionHandler(InvalidTransitionException.class, 
this::badRequest),
+                newExceptionHandler(NodeInstanceNotFoundException.class, 
nodeInstanceNotFoundMessageException(), this::notFound),
+                newExceptionHandler(NodeNotFoundException.class, 
nodeNotFoundMessageException(), this::notFound),
+                newExceptionHandler(NotAuthorizedException.class, 
this::forbidden),
+                newExceptionHandler(ProcessInstanceDuplicatedException.class, 
processInstanceDuplicatedMessageException(), this::conflict),
+                newExceptionHandler(ProcessInstanceExecutionException.class, 
processInstanceExecutionMessageException(), this::internalError),
+                newExceptionHandler(ProcessInstanceNotFoundException.class, 
processInstanceNotFoundExceptionMessageException(), this::notFound),
+                newExceptionHandler(WorkItemNotFoundException.class, 
workItemNotFoundMessageException(), this::notFound),
+                newExceptionHandler(VariableViolationException.class, 
variableViolationMessageException(), this::badRequest),
+                newExceptionHandler(WorkItemExecutionException.class, 
workItemExecutionMessageException(), this::fromErrorCode),
+                newExceptionHandler(IllegalArgumentException.class, 
this::badRequest),
+                newExceptionHandler(MessageException.class, this::badRequest));
+
+        this.mapper = new HashMap<>();
+        for (RestExceptionHandler<? extends Throwable, T> handler : handlers) {
+            this.mapper.put(handler.getType(), handler);
+        }
+        this.errorHandlers = new ArrayList<>();
+        errorHandlers.iterator().forEachRemaining(this.errorHandlers::add);
+
+    }
+
+    private T fromErrorCode(ExceptionBodyMessage message) {
+        switch (message.getErrorCode()) {
+            case "400":
+                return badRequest(message);
+            case "403":
+                return forbidden(message);
+            case "404":
+                return notFound(message);
+            case "409":
+                return conflict(message);
+            default:
+                return internalError(message);
+        }
+    }
+
+    protected abstract T badRequest(ExceptionBodyMessage body);
+
+    protected abstract T conflict(ExceptionBodyMessage body);
+
+    protected abstract T internalError(ExceptionBodyMessage body);
+
+    protected abstract T notFound(ExceptionBodyMessage body);
+
+    protected abstract T forbidden(ExceptionBodyMessage body);
+
+    public T mapException(Exception exceptionThrown) {
+
+        var handler = mapper.getOrDefault(exceptionThrown.getClass(), 
DEFAULT_HANDLER);
+        ExceptionBodyMessage message = handler.getContent(exceptionThrown);
+
+        Throwable rootCause = exceptionThrown.getCause();
+        while (rootCause != null) {
+            if (mapper.containsKey(rootCause.getClass())) {
+                handler = mapper.get(rootCause.getClass());
+                message.merge(handler.getContent(rootCause));
+            }
+            rootCause = rootCause.getCause();
+        }
+        // we invoked the error handlers
+        errorHandlers.forEach(e -> e.handle(exceptionThrown));
+        T response = handler.buildResponse(message);
+        LOG.debug("mapping exception {} with response {}", exceptionThrown, 
message.getBody());
+        return response;
+    }
+}
diff --git 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java
 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java
deleted file mode 100644
index c326eeebe7..0000000000
--- 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java
+++ /dev/null
@@ -1,203 +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 org.kie.kogito.resource.exceptions;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-
-import org.kie.kogito.internal.process.runtime.MessageException;
-import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException;
-import org.kie.kogito.internal.process.workitem.InvalidTransitionException;
-import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
-import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
-import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException;
-import org.kie.kogito.process.NodeInstanceNotFoundException;
-import org.kie.kogito.process.NodeNotFoundException;
-import org.kie.kogito.process.ProcessInstanceDuplicatedException;
-import org.kie.kogito.process.ProcessInstanceExecutionException;
-import org.kie.kogito.process.ProcessInstanceNotFoundException;
-import org.kie.kogito.process.VariableViolationException;
-import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException;
-import org.kie.kogito.usertask.UserTaskInstanceNotFoundException;
-import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException;
-
-public abstract class BaseExceptionsHandler<T> {
-
-    public static final String MESSAGE = "message";
-    public static final String PROCESS_INSTANCE_ID = "processInstanceId";
-    private static final String TASK_ID = "taskId";
-    public static final String VARIABLE = "variable";
-    public static final String NODE_INSTANCE_ID = "nodeInstanceId";
-    public static final String NODE_ID = "nodeId";
-    public static final String FAILED_NODE_ID = "failedNodeId";
-    public static final String ID = "id";
-    private final Map<Class<? extends Exception>, FunctionHolder<T, ?>> mapper;
-
-    private static class FunctionHolder<T, R> {
-        private final Function<Exception, R> contentGenerator;
-        private final Function<Exception, Function<R, T>> responseGenerator;
-
-        public FunctionHolder(Function<Exception, R> contentGenerator, 
Function<Exception, Function<R, T>> responseGenerator) {
-            this.contentGenerator = contentGenerator;
-            this.responseGenerator = responseGenerator;
-        }
-
-        public Function<Exception, R> getContentGenerator() {
-            return contentGenerator;
-        }
-
-        public Function<Exception, Function<R, T>> getResponseGenerator() {
-            return responseGenerator;
-        }
-    }
-
-    private final FunctionHolder<T, Exception> defaultHolder = new 
FunctionHolder<>(ex -> ex, ex -> BaseExceptionsHandler.this::internalError);
-    private final FunctionHolder<T, ?> messageFunctionHolder = new 
FunctionHolder<>(ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::badRequest);
-
-    protected BaseExceptionsHandler() {
-        mapper = new HashMap<>();
-        mapper.put(InvalidLifeCyclePhaseException.class, new FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::badRequest));
-
-        mapper.put(UserTaskTransitionException.class, new FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::badRequest));
-
-        mapper.put(UserTaskInstanceNotFoundException.class, new 
FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::notFound));
-
-        mapper.put(UserTaskInstanceNotAuthorizedException.class, new 
FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::forbidden));
-
-        mapper.put(InvalidTransitionException.class, new FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::badRequest));
-
-        mapper.put(NodeInstanceNotFoundException.class, new FunctionHolder<>(
-                ex -> {
-                    NodeInstanceNotFoundException exception = 
(NodeInstanceNotFoundException) ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(MESSAGE, exception.getMessage());
-                    response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
-                    response.put(NODE_INSTANCE_ID, 
exception.getNodeInstanceId());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::notFound));
-
-        mapper.put(NodeNotFoundException.class, new FunctionHolder<>(
-                ex -> {
-                    NodeNotFoundException exception = (NodeNotFoundException) 
ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(MESSAGE, exception.getMessage());
-                    response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
-                    response.put(NODE_ID, exception.getNodeId());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::notFound));
-
-        mapper.put(NotAuthorizedException.class, new FunctionHolder<>(
-                ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex 
-> BaseExceptionsHandler.this::forbidden));
-
-        mapper.put(ProcessInstanceDuplicatedException.class, new 
FunctionHolder<>(
-                ex -> {
-                    ProcessInstanceDuplicatedException exception = 
(ProcessInstanceDuplicatedException) ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(MESSAGE, exception.getMessage());
-                    response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::conflict));
-
-        mapper.put(ProcessInstanceExecutionException.class, new 
FunctionHolder<>(
-                ex -> {
-                    ProcessInstanceExecutionException exception = 
(ProcessInstanceExecutionException) ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(ID, exception.getProcessInstanceId());
-                    response.put(FAILED_NODE_ID, exception.getFailedNodeId());
-                    response.put(MESSAGE, exception.getErrorMessage());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::internalError));
-
-        mapper.put(ProcessInstanceNotFoundException.class, new 
FunctionHolder<>(
-                ex -> {
-                    ProcessInstanceNotFoundException exception = 
(ProcessInstanceNotFoundException) ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(MESSAGE, exception.getMessage());
-                    response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::notFound));
-
-        mapper.put(WorkItemNotFoundException.class, new FunctionHolder<>(ex -> 
{
-            WorkItemNotFoundException exception = (WorkItemNotFoundException) 
ex;
-            return Map.of(MESSAGE, exception.getMessage(), TASK_ID, 
exception.getWorkItemId());
-        }, ex -> BaseExceptionsHandler.this::notFound));
-
-        mapper.put(VariableViolationException.class, new FunctionHolder<>(
-                ex -> {
-                    VariableViolationException exception = 
(VariableViolationException) ex;
-                    Map<String, String> response = new HashMap<>();
-                    response.put(MESSAGE, exception.getMessage() + " : " + 
exception.getErrorMessage());
-                    response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
-                    response.put(VARIABLE, exception.getVariableName());
-                    return response;
-                }, ex -> BaseExceptionsHandler.this::badRequest));
-
-        mapper.put(WorkItemExecutionException.class, new FunctionHolder<>(
-                ex -> Map.of(MESSAGE, ex.getMessage()),
-                ex -> fromErrorCode(((WorkItemExecutionException) 
ex).getErrorCode())));
-        mapper.put(IllegalArgumentException.class, messageFunctionHolder);
-        mapper.put(MessageException.class, messageFunctionHolder);
-    }
-
-    private <R> Function<R, T> fromErrorCode(String errorCode) {
-        switch (errorCode) {
-            case "400":
-                return this::badRequest;
-            case "403":
-                return this::forbidden;
-            case "404":
-                return this::notFound;
-            case "409":
-                return this::conflict;
-            default:
-                return this::internalError;
-        }
-    }
-
-    protected abstract <R> T badRequest(R body);
-
-    protected abstract <R> T conflict(R body);
-
-    protected abstract <R> T internalError(R body);
-
-    protected abstract <R> T notFound(R body);
-
-    protected abstract <R> T forbidden(R body);
-
-    public <R extends Exception, U> T mapException(R exception) {
-        FunctionHolder<T, U> holder = (FunctionHolder<T, U>) 
mapper.getOrDefault(exception.getClass(), defaultHolder);
-        U body = holder.getContentGenerator().apply(exception);
-        Throwable rootCause = exception.getCause();
-        while (rootCause != null) {
-            if (mapper.containsKey(rootCause.getClass())) {
-                holder = (FunctionHolder<T, U>) 
mapper.get(rootCause.getClass());
-                exception = (R) rootCause;
-            }
-            rootCause = rootCause.getCause();
-        }
-        return holder.getResponseGenerator().apply(exception).apply(body);
-    }
-}
diff --git 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java
 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java
new file mode 100644
index 0000000000..b491b111f4
--- /dev/null
+++ 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java
@@ -0,0 +1,57 @@
+/*
+ * 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.resource.exceptions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ExceptionBodyMessage {
+
+    public static final String MESSAGE = "message";
+    public static final String PROCESS_INSTANCE_ID = "processInstanceId";
+    public static final String TASK_ID = "taskId";
+    public static final String VARIABLE = "variable";
+    public static final String NODE_INSTANCE_ID = "nodeInstanceId";
+    public static final String NODE_ID = "nodeId";
+    public static final String FAILED_NODE_ID = "failedNodeId";
+    public static final String ID = "id";
+    public static final String ERROR_CODE = "errorCode";
+
+    private Map<String, String> body;
+
+    public ExceptionBodyMessage() {
+        body = new HashMap<>();
+    }
+
+    public ExceptionBodyMessage(Map<String, String> body) {
+        this.body = new HashMap<>(body);
+    }
+
+    public Map<String, String> getBody() {
+        return body;
+    }
+
+    public String getErrorCode() {
+        return body.getOrDefault(ERROR_CODE, "");
+    }
+
+    public void merge(ExceptionBodyMessage content) {
+        this.body.putAll(content.body);
+    }
+}
diff --git 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java
 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java
new file mode 100644
index 0000000000..0bc1e6fd81
--- /dev/null
+++ 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java
@@ -0,0 +1,124 @@
+/*
+ * 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.resource.exceptions;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
+import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException;
+import org.kie.kogito.process.NodeInstanceNotFoundException;
+import org.kie.kogito.process.NodeNotFoundException;
+import org.kie.kogito.process.ProcessInstanceDuplicatedException;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
+import org.kie.kogito.process.ProcessInstanceNotFoundException;
+import org.kie.kogito.process.VariableViolationException;
+
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessage.ERROR_CODE;
+
+public class ExceptionBodyMessageFunctions {
+
+    public static final String MESSAGE = "message";
+    public static final String PROCESS_INSTANCE_ID = "processInstanceId";
+    public static final String TASK_ID = "taskId";
+    public static final String VARIABLE = "variable";
+    public static final String NODE_INSTANCE_ID = "nodeInstanceId";
+    public static final String NODE_ID = "nodeId";
+    public static final String FAILED_NODE_ID = "failedNodeId";
+    public static final String ID = "id";
+
+    public static <T extends Exception> Function<T, ExceptionBodyMessage> 
defaultMessageException() {
+        return ex -> new 
ExceptionBodyMessage(Collections.singletonMap(MESSAGE, ex.getMessage()));
+    }
+
+    public static Function<NodeInstanceNotFoundException, 
ExceptionBodyMessage> nodeInstanceNotFoundMessageException() {
+        return ex -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, ex.getMessage());
+            response.put(PROCESS_INSTANCE_ID, ex.getProcessInstanceId());
+            response.put(NODE_INSTANCE_ID, ex.getNodeInstanceId());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<NodeNotFoundException, ExceptionBodyMessage> 
nodeNotFoundMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, exception.getMessage());
+            response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
+            response.put(NODE_ID, exception.getNodeId());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<ProcessInstanceDuplicatedException, 
ExceptionBodyMessage> processInstanceDuplicatedMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, exception.getMessage());
+            response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<ProcessInstanceExecutionException, 
ExceptionBodyMessage> processInstanceExecutionMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(ID, exception.getProcessInstanceId());
+            response.put(FAILED_NODE_ID, exception.getFailedNodeId());
+            response.put(MESSAGE, exception.getErrorMessage());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<ProcessInstanceNotFoundException, 
ExceptionBodyMessage> processInstanceNotFoundExceptionMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, exception.getMessage());
+            response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<WorkItemNotFoundException, ExceptionBodyMessage> 
workItemNotFoundMessageException() {
+        return exception -> {
+            return new ExceptionBodyMessage(Map.of(MESSAGE, 
exception.getMessage(), TASK_ID, exception.getWorkItemId()));
+        };
+    }
+
+    public static Function<WorkItemExecutionException, ExceptionBodyMessage> 
workItemExecutionMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, exception.getMessage());
+            response.put(ERROR_CODE, exception.getErrorCode());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+
+    public static Function<VariableViolationException, ExceptionBodyMessage> 
variableViolationMessageException() {
+        return exception -> {
+            Map<String, String> response = new HashMap<>();
+            response.put(MESSAGE, exception.getMessage() + " : " + 
exception.getErrorMessage());
+            response.put(PROCESS_INSTANCE_ID, 
exception.getProcessInstanceId());
+            response.put(VARIABLE, exception.getVariableName());
+            return new ExceptionBodyMessage(response);
+        };
+    }
+}
diff --git 
a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java
 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java
new file mode 100644
index 0000000000..083e4efe83
--- /dev/null
+++ 
b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.resource.exceptions;
+
+import java.util.function.Function;
+
+import static 
org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.defaultMessageException;
+
+public class RestExceptionHandler<EXCEPTION extends Throwable, RESPONSE> {
+    private final Function<EXCEPTION, ExceptionBodyMessage> messageConverter;
+
+    private final Function<ExceptionBodyMessage, RESPONSE> responseConverter;
+
+    private Class<EXCEPTION> type;
+
+    public RestExceptionHandler(Class<EXCEPTION> type, Function<EXCEPTION, 
ExceptionBodyMessage> messageConverter, Function<ExceptionBodyMessage, 
RESPONSE> responseConverter) {
+        this.type = type;
+        this.messageConverter = messageConverter;
+        this.responseConverter = responseConverter;
+    }
+
+    public Class<EXCEPTION> getType() {
+        return type;
+    }
+
+    public ExceptionBodyMessage getContent(Throwable exception) {
+        return messageConverter.apply(getType().cast(exception));
+    }
+
+    public RESPONSE buildResponse(ExceptionBodyMessage exceptionBodyMessage) {
+        return responseConverter.apply(exceptionBodyMessage);
+    }
+
+    public static <TYPE extends Exception, RES> RestExceptionHandler<TYPE, 
RES> newExceptionHandler(Class<TYPE> type, Function<TYPE, ExceptionBodyMessage> 
contentGenerator,
+            Function<ExceptionBodyMessage, RES> responseGenerator) {
+        return new RestExceptionHandler<TYPE, RES>(type, contentGenerator, 
responseGenerator);
+    }
+
+    public static <F extends Exception, R> RestExceptionHandler<F, R> 
newExceptionHandler(Class<F> type, Function<ExceptionBodyMessage, R> 
responseGenerator) {
+        return new RestExceptionHandler<F, R>(type, defaultMessageException(), 
responseGenerator);
+    }
+}
\ No newline at end of file
diff --git 
a/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java
 
b/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java
index 01f244915d..bf8f28fc31 100644
--- 
a/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java
+++ 
b/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java
@@ -40,7 +40,7 @@ import static org.mockito.Mockito.spy;
 @ExtendWith(MockitoExtension.class)
 class BaseExceptionHandlerTest {
 
-    private BaseExceptionsHandler tested;
+    private AbstractExceptionsHandler<Object> tested;
 
     @Mock
     private Object badRequestResponse;
@@ -59,29 +59,29 @@ class BaseExceptionHandlerTest {
 
     @BeforeEach
     void setUp() {
-        tested = spy(new BaseExceptionsHandler() {
+        tested = spy(new AbstractExceptionsHandler<Object>() {
             @Override
-            protected Object badRequest(Object body) {
+            protected Object badRequest(ExceptionBodyMessage body) {
                 return badRequestResponse;
             }
 
             @Override
-            protected Object conflict(Object body) {
+            protected Object conflict(ExceptionBodyMessage body) {
                 return conflictResponse;
             }
 
             @Override
-            protected Object internalError(Object body) {
+            protected Object internalError(ExceptionBodyMessage body) {
                 return internalErrorResponse;
             }
 
             @Override
-            protected Object notFound(Object body) {
+            protected Object notFound(ExceptionBodyMessage body) {
                 return notFoundResponse;
             }
 
             @Override
-            protected Object forbidden(Object body) {
+            protected Object forbidden(ExceptionBodyMessage body) {
                 return forbiddenResponse;
             }
         });
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
 b/api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java
similarity index 69%
copy from 
quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
copy to 
api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java
index 56a734a511..7bfbdf37ef 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java
@@ -16,16 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.kie.kogito.resource.exceptions;
+package org.kie.kogito.handler;
 
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.ext.Provider;
+public interface ExceptionHandler {
 
-@Provider
-public class IllegalArgumentExceptionMapper extends 
BaseExceptionMapper<IllegalArgumentException> {
+    void handle(Exception th);
 
-    @Override
-    public Response toResponse(IllegalArgumentException e) {
-        return exceptionsHandler.mapException(e);
-    }
 }
diff --git 
a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java 
b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java
index 644f9fe25a..72180a45c1 100755
--- a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java
+++ b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java
@@ -107,6 +107,8 @@ import org.slf4j.LoggerFactory;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 public class ProcessHandler extends BaseAbstractHandler implements Handler {
 
     private static final Logger logger = 
LoggerFactory.getLogger(ProcessHandler.class);
@@ -412,7 +414,7 @@ public class ProcessHandler extends BaseAbstractHandler 
implements Handler {
                 result.setMetaData("bendpoints", connection.getBendpoints());
                 result.setMetaData(Metadata.UNIQUE_ID, connection.getId());
 
-                if (source instanceof NodeImpl nodeImpl && 
Boolean.parseBoolean((String) 
process.getMetaData().get("jbpm.enable.multi.con"))) {
+                if (source instanceof NodeImpl nodeImpl && 
WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(process)) {
                     Constraint constraint = buildConstraint(connection, 
nodeImpl);
                     if (constraint != null) {
                         nodeImpl.addConstraint(new 
ConnectionRef(connection.getId(), target.getId(), 
org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE),
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java
index 0ea5dee550..13a21788e1 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java
@@ -40,6 +40,8 @@ import org.kie.api.definition.process.Connection;
 import org.kie.api.definition.process.NodeContainer;
 import org.kie.api.definition.process.WorkflowElementIdentifier;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a node.
  */
@@ -297,7 +299,7 @@ public abstract class NodeImpl implements Node, 
ContextResolver, Mappable {
         if (list.size() == 1) {
             return list.get(0);
         }
-        if (Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             return list.get(0);
         } else {
             throw new IllegalArgumentException(
@@ -317,7 +319,7 @@ public abstract class NodeImpl implements Node, 
ContextResolver, Mappable {
         if (list.size() == 1) {
             return list.get(0);
         }
-        if (Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             return list.get(0);
         } else {
             throw new IllegalArgumentException(
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java
index 9144d42833..53f74b4143 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java
@@ -27,6 +27,8 @@ import org.jbpm.workflow.core.impl.DataAssociation;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of an action node.
  * 
@@ -53,7 +55,7 @@ public class ActionNode extends ExtendedNodeImpl {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -68,7 +70,7 @@ public class ActionNode extends ExtendedNodeImpl {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java
index ae88f43956..222f13d1c7 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java
@@ -22,6 +22,8 @@ import org.jbpm.workflow.core.Node;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of an end node.
  * 
@@ -58,7 +60,7 @@ public class EndNode extends ExtendedNodeImpl {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java
index 42d216f483..0ce70b86f8 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java
@@ -28,6 +28,8 @@ import org.jbpm.workflow.core.Node;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 public class EventNode extends ExtendedNodeImpl implements EventNodeInterface {
 
     private static final long serialVersionUID = 510l;
@@ -104,7 +106,7 @@ public class EventNode extends ExtendedNodeImpl implements 
EventNodeInterface {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -119,7 +121,7 @@ public class EventNode extends ExtendedNodeImpl implements 
EventNodeInterface {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java
index 7ffabedcda..1a1ba697ff 100755
--- 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java
+++ 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java
@@ -24,6 +24,8 @@ import org.jbpm.workflow.core.Node;
 import org.kie.api.definition.process.Connection;
 import org.kie.api.runtime.process.ProcessContext;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a milestone node.
  */
@@ -59,7 +61,7 @@ public class MilestoneNode extends StateBasedNode implements 
Constrainable {
         if (!Node.CONNECTION_DEFAULT_TYPE.equals(type)) {
             throwValidationException(connection, "only accepts default 
incoming connection type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throwValidationException(connection, "cannot have more than one 
incoming connection!");
         }
     }
@@ -70,7 +72,7 @@ public class MilestoneNode extends StateBasedNode implements 
Constrainable {
         if (!Node.CONNECTION_DEFAULT_TYPE.equals(type)) {
             throwValidationException(connection, "only accepts default 
outgoing connection type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throwValidationException(connection, "cannot have more than one 
outgoing connection!");
         }
     }
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java
index 3d129f3b22..b7d9f87207 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java
@@ -35,6 +35,7 @@ import org.kie.api.definition.process.Connection;
 import org.kie.api.runtime.KieRuntime;
 import org.kie.kogito.decision.DecisionModel;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
 import static org.jbpm.workflow.instance.rule.RuleType.DRL_LANG;
 
 /**
@@ -105,7 +106,7 @@ public class RuleSetNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -120,7 +121,7 @@ public class RuleSetNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java
index fe2114905c..77c4a8e717 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java
@@ -26,6 +26,8 @@ import org.jbpm.workflow.core.impl.ConnectionRef;
 import org.jbpm.workflow.core.impl.NodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a split node.
  * 
@@ -143,7 +145,7 @@ public class Split extends NodeImpl implements 
Constrainable {
                             + "] only accepts default incoming connection 
type!");
         }
 
-        if (!getIncomingConnections(Node.CONNECTION_DEFAULT_TYPE).isEmpty() && 
!Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (!getIncomingConnections(Node.CONNECTION_DEFAULT_TYPE).isEmpty() && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java
index 3743bbd157..e6e43a2bb3 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java
@@ -27,6 +27,8 @@ import org.jbpm.workflow.core.Node;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a start node.
  * 
@@ -92,7 +94,7 @@ public class StartNode extends ExtendedNodeImpl {
             throw new IllegalArgumentException(
                     "A start node [" + this.getUniqueId() + ", " + 
this.getName() + "] only accepts default outgoing connection type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "A start node [" + this.getUniqueId() + ", " + 
this.getName() + "] cannot have more than one outgoing connection!");
         }
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
index 77f873ddc7..7dcad36021 100755
--- 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
+++ 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
@@ -27,6 +27,8 @@ import org.jbpm.process.core.impl.ContextContainerImpl;
 import org.jbpm.workflow.core.Node;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a sub-flow node.
  * 
@@ -77,7 +79,7 @@ public class SubProcessNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -92,7 +94,7 @@ public class SubProcessNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java
index e8ce9a5495..5096a47f0e 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java
@@ -23,6 +23,8 @@ import org.jbpm.workflow.core.Node;
 import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 public class TimerNode extends ExtendedNodeImpl {
 
     private static final long serialVersionUID = 510l;
@@ -45,7 +47,7 @@ public class TimerNode extends ExtendedNodeImpl {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -60,7 +62,7 @@ public class TimerNode extends ExtendedNodeImpl {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java
index d7cd3c06c1..5eb00999d0 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java
@@ -28,6 +28,8 @@ import org.jbpm.process.core.impl.ContextContainerImpl;
 import org.jbpm.workflow.core.Node;
 import org.kie.api.definition.process.Connection;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Default implementation of a task node.
  * 
@@ -66,7 +68,7 @@ public class WorkItemNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] only accepts default incoming connection 
type!");
         }
-        if (getFrom() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getFrom() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getTo().getUniqueId() + 
", " + connection.getTo().getName()
                             + "] cannot have more than one incoming 
connection!");
@@ -81,7 +83,7 @@ public class WorkItemNode extends StateBasedNode implements 
ContextContainer {
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] only accepts default outgoing connection 
type!");
         }
-        if (getTo() != null && !Boolean.parseBoolean((String) 
getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+        if (getTo() != null && 
!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) {
             throw new IllegalArgumentException(
                     "This type of node [" + connection.getFrom().getUniqueId() 
+ ", " + connection.getFrom().getName()
                             + "] cannot have more than one outgoing 
connection!");
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java
 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java
new file mode 100644
index 0000000000..cf5c1bd611
--- /dev/null
+++ 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jbpm.workflow.instance;
+
+import java.util.function.Function;
+
+public class WorkflowProcessParameters {
+
+    /**
+     * Allows activities to have multiple outgoing connections
+     */
+    public static final WorkflowProcessParameter<Boolean> 
WORKFLOW_PARAM_MULTIPLE_CONNECTIONS = 
newBooleanParameter("jbpm.enable.multi.con");
+    public static final WorkflowProcessParameter<Boolean> 
WORKFLOW_PARAM_TRANSACTIONS = newBooleanParameter("jbpm.transactions.enable");
+
+    public static WorkflowProcessParameter<String> newStringParameter(String 
name) {
+        return new WorkflowProcessParameter<String>(name, Function.identity());
+    }
+
+    public static WorkflowProcessParameter<Boolean> newBooleanParameter(String 
name) {
+        return new WorkflowProcessParameter<Boolean>(name, 
Boolean::parseBoolean);
+    }
+
+    public static class WorkflowProcessParameter<T> {
+        private String name;
+        private Function<String, T> converter;
+
+        WorkflowProcessParameter(String name, Function<String, T> converter) {
+            this.name = name;
+            this.converter = converter;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public T get(org.kie.api.definition.process.Process workflowProcess) {
+            return converter.apply((String) 
workflowProcess.getMetaData().get(name));
+        }
+    }
+}
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
index fb73d73c00..fd78e89418 100755
--- 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
+++ 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
@@ -61,12 +61,15 @@ import 
org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
 import org.kie.kogito.internal.process.runtime.KogitoNodeInstanceContainer;
 import org.kie.kogito.internal.process.runtime.KogitoProcessContext;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.jbpm.ruleflow.core.Metadata.HIDDEN;
 import static org.jbpm.ruleflow.core.Metadata.INCOMING_CONNECTION;
 import static org.jbpm.ruleflow.core.Metadata.OUTGOING_CONNECTION;
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS;
 import static 
org.kie.kogito.internal.process.runtime.KogitoProcessInstance.STATE_ACTIVE;
 
 /**
@@ -248,10 +251,15 @@ public abstract class NodeInstanceImpl implements 
org.jbpm.workflow.instance.Nod
         try {
             internalTrigger(from, type);
         } catch (Exception e) {
-            logger.debug("Node instance causing process instance error in id 
{}", this.getStringId(), e);
-            captureError(e);
+            if 
(!WORKFLOW_PARAM_TRANSACTIONS.get(getProcessInstance().getProcess())) {
+                logger.error("Node instance causing process instance error in 
id {} in a non transactional environment", this.getStringId());
+                captureError(e);
+                return;
+            } else {
+                logger.error("Node instance causing process instance error in 
id {} in a transactional environment (Wrapping)", this.getStringId());
+                throw new 
ProcessInstanceExecutionException(this.getProcessInstance().getId(), 
this.getNodeDefinitionId(), e.getMessage(), e);
+            }
             // stop after capturing error
-            return;
         }
         if (!hidden) {
             ((InternalProcessRuntime) kruntime.getProcessRuntime())
@@ -260,8 +268,6 @@ public abstract class NodeInstanceImpl implements 
org.jbpm.workflow.instance.Nod
     }
 
     protected void captureError(Exception e) {
-        logger.error("capture error", e);
-        e.printStackTrace();
         getProcessInstance().setErrorState(this, e);
     }
 
@@ -317,7 +323,7 @@ public abstract class NodeInstanceImpl implements 
org.jbpm.workflow.instance.Nod
 
         List<Connection> connections = null;
         if (node != null) {
-            if (Boolean.parseBoolean((String) 
getProcessInstance().getProcess().getMetaData().get("jbpm.enable.multi.con")) 
&& !((NodeImpl) node).getConstraints().isEmpty()) {
+            if 
(WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcessInstance().getProcess()) && 
!((NodeImpl) node).getConstraints().isEmpty()) {
                 int priority;
                 connections = ((NodeImpl) 
node).getDefaultOutgoingConnections();
                 boolean found = false;
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 48a7276e26..e185f6d019 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
@@ -100,6 +100,7 @@ import org.kie.kogito.process.BaseEventDescription;
 import org.kie.kogito.process.EventDescription;
 import org.kie.kogito.process.NamedDataType;
 import org.kie.kogito.process.ProcessInstance;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
 import org.kie.kogito.process.flexible.AdHocFragment;
 import org.kie.kogito.process.flexible.ItemDescription;
 import org.kie.kogito.process.flexible.Milestone;
@@ -1316,6 +1317,15 @@ public abstract class WorkflowProcessInstanceImpl 
extends ProcessInstanceImpl im
         this.errorCause = Optional.empty();
     }
 
+    public void internalSetError(ProcessInstanceExecutionException e) {
+        this.nodeIdInError = e.getFailedNodeId();
+        Throwable rootException = getRootException(e);
+        this.errorMessage = rootException instanceof MessageException ? 
rootException.getMessage() : rootException.getClass().getCanonicalName() + " - 
" + rootException.getMessage();
+        this.errorCause = Optional.of(e);
+        setState(STATE_ERROR);
+        ((InternalProcessRuntime) 
getKnowledgeRuntime().getProcessRuntime()).getProcessEventSupport().fireOnError(this,
 null, getKnowledgeRuntime(), e);
+    }
+
     @Override
     public Collection<AdHocFragment> adHocFragments() {
         return Stream.of(getNodeContainer().getNodes())
diff --git 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java
 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java
index 9b17022e46..4749aee86e 100755
--- 
a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java
+++ 
b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java
@@ -54,6 +54,8 @@ import 
org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
 import org.mvel2.integration.VariableResolver;
 import org.mvel2.integration.impl.SimpleValueResolver;
 
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS;
+
 /**
  * Runtime counterpart of a for each node.
  */
@@ -297,7 +299,7 @@ public class ForEachNodeInstance extends 
CompositeContextNodeInstance {
                 ((NodeInstanceContainer) 
getNodeInstanceContainer()).removeNodeInstance(this);
 
                 if (getForEachNode().isWaitForCompletion()) {
-                    if (!Boolean.parseBoolean((String) 
getForEachNode().getProcess().getMetaData().get("jbpm.enable.multi.con"))) {
+                    if 
(!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcessInstance().getProcess())) {
                         triggerConnection(getForEachJoinNode().getTo());
                     } else {
                         List<Connection> connections = 
getForEachJoinNode().getOutgoingConnections(Node.CONNECTION_DEFAULT_TYPE);
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java
 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java
index 9c84008fc5..7737a67aca 100644
--- 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java
+++ 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java
@@ -34,6 +34,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -82,6 +83,7 @@ import static org.jbpm.ruleflow.core.Metadata.TRIGGER_REF;
 import static org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE;
 import static org.jbpm.workflow.core.impl.ExtendedNodeImpl.EVENT_NODE_ENTER;
 import static org.jbpm.workflow.core.impl.ExtendedNodeImpl.EVENT_NODE_EXIT;
+import static 
org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
@@ -108,7 +110,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class ProcessGenerationIT extends AbstractCodegenIT {
 
     private static final Collection<String> IGNORED_PROCESS_META =
-            Arrays.asList("Definitions", "BPMN.Connections", 
"BPMN.Associations", "ItemDefinitions");
+            Arrays.asList("Definitions", "BPMN.Connections", 
"BPMN.Associations", "ItemDefinitions", WORKFLOW_PARAM_TRANSACTIONS.getName());
     private static final Path BASE_PATH = Paths.get("src/test/resources");
 
     static Stream<String> processesProvider() throws IOException {
@@ -405,13 +407,15 @@ public class ProcessGenerationIT extends 
AbstractCodegenIT {
             return;
         }
         expected.remove("CorrelationSubscriptions");
-        assertThat(current).hasSize((int) expected.keySet()
-                .stream()
-                .filter(k -> ignoredKeys == null || !ignoredKeys.contains(k))
-                .count());
+        Predicate<String> precicateIgnoredKeys = 
Predicate.not(ignoredKeys::contains);
+
+        List<String> currentKeys = 
current.keySet().stream().filter(precicateIgnoredKeys).toList();
+        List<String> expectedKeys = 
expected.keySet().stream().filter(precicateIgnoredKeys).toList();
+        assertThat(currentKeys).containsExactlyElementsOf(expectedKeys);
+
         expected.keySet()
                 .stream()
-                .filter(k -> ignoredKeys == null || !ignoredKeys.contains(k))
+                .filter(precicateIgnoredKeys)
                 .forEach(k -> assertThat(current).as("Metadata " + 
k).containsEntry(k, expected.get(k)));
     }
 
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 14bab62579..20c7189198 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
@@ -48,6 +48,8 @@ import org.jbpm.compiler.xml.XmlProcessReader;
 import org.jbpm.compiler.xml.core.SemanticModules;
 import org.jbpm.process.core.impl.ProcessImpl;
 import org.jbpm.process.core.validation.ProcessValidatorRegistry;
+import org.jbpm.workflow.core.impl.WorkflowProcessImpl;
+import org.jbpm.workflow.instance.WorkflowProcessParameters;
 import org.kie.api.definition.process.Process;
 import org.kie.api.definition.process.WorkflowProcess;
 import org.kie.api.io.Resource;
@@ -57,7 +59,9 @@ import org.kie.kogito.codegen.api.GeneratedInfo;
 import org.kie.kogito.codegen.api.SourceFileCodegenBindEvent;
 import org.kie.kogito.codegen.api.context.ContextAttributesConstants;
 import org.kie.kogito.codegen.api.context.KogitoBuildContext;
+import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext;
 import org.kie.kogito.codegen.api.io.CollectedResource;
+import org.kie.kogito.codegen.api.template.TemplatedGenerator;
 import org.kie.kogito.codegen.core.AbstractGenerator;
 import org.kie.kogito.codegen.core.DashboardGeneratedFileUtils;
 import org.kie.kogito.codegen.process.config.ProcessConfigGenerator;
@@ -74,11 +78,13 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
 
+import com.github.javaparser.ast.CompilationUnit;
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
 
 import static java.lang.String.format;
 import static java.util.stream.Collectors.toList;
 import static 
org.jbpm.process.core.constants.CalendarConstants.BUSINESS_CALENDAR_PATH;
+import static 
org.kie.kogito.codegen.process.util.CodegenUtil.isTransactionEnabled;
 import static 
org.kie.kogito.grafana.GrafanaConfigurationWriter.buildDashboardName;
 import static 
org.kie.kogito.grafana.GrafanaConfigurationWriter.generateOperationalDashboard;
 import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName;
@@ -297,6 +303,12 @@ public class ProcessCodegen extends AbstractGenerator {
 
         // first we generate all the data classes from variable declarations
         for (WorkflowProcess workFlowProcess : processes.values()) {
+            // transaction is disabled by default for SW types
+            boolean defaultTransactionEnabled = 
!KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType());
+            if (isTransactionEnabled(this, context(), 
defaultTransactionEnabled)) {
+                ((WorkflowProcessImpl) 
workFlowProcess).setMetaData(WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS.getName(),
 "true");
+            }
+
             if (!skipModelGeneration(workFlowProcess)) {
                 ModelClassGenerator mcg = new ModelClassGenerator(context(), 
workFlowProcess);
                 processIdToModelGenerator.put(workFlowProcess.getId(), mcg);
@@ -308,9 +320,10 @@ public class ProcessCodegen extends AbstractGenerator {
                 processIdToOutputModelGenerator.put(workFlowProcess.getId(), 
omcg);
             }
         }
-
+        boolean isServerless = false;
         // then we generate work items task inputs and outputs if any
         for (WorkflowProcess workFlowProcess : processes.values()) {
+            isServerless |= 
KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType());
             if 
(KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType())) {
                 continue;
             }
@@ -339,6 +352,7 @@ public class ProcessCodegen extends AbstractGenerator {
         }
 
         // generate Process, ProcessInstance classes and the REST resource
+
         for (ProcessExecutableModelGenerator execModelGen : 
processExecutableModelGenerators) {
             String classPrefix = 
sanitizeClassName(execModelGen.extractedProcessId());
             KogitoWorkflowProcess workFlowProcess = execModelGen.process();
@@ -373,7 +387,7 @@ public class ProcessCodegen extends AbstractGenerator {
                         
.withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId()))
                         .withSignals(metaData.getSignals())
                         .withTriggers(metaData.isStartable(), 
metaData.isDynamic(), metaData.getTriggers())
-                        
.withTransaction(CodegenUtil.isTransactionEnabled(this, context()));
+                        .withTransaction(isTransactionEnabled(this, 
context()));
 
                 rgs.add(processResourceGenerator);
             }
@@ -451,6 +465,17 @@ public class ProcessCodegen extends AbstractGenerator {
                     .forEach((key, value) -> storeFile(PRODUCER_TYPE, key, 
value));
         }
 
+        if (CodegenUtil.isTransactionEnabled(this, context()) && 
!isServerless) {
+            String template = "ExceptionHandlerTransaction";
+            TemplatedGenerator generator = TemplatedGenerator.builder()
+                    .withTemplateBasePath("/class-templates/transaction/")
+                    .withFallbackContext(JavaKogitoBuildContext.CONTEXT_NAME)
+                    .withTargetTypeName(template)
+                    .build(context(), template);
+            CompilationUnit handler = generator.compilationUnitOrThrow();
+            storeFile(MODEL_TYPE, generator.generatedFilePath(), 
handler.toString());
+        }
+
         if (context().hasRESTForGenerator(this)) {
             for (ProcessResourceGenerator resourceGenerator : rgs) {
                 storeFile(REST_TYPE, resourceGenerator.generatedFilePath(),
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java
 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java
index ecf9737e17..1e33a95861 100644
--- 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java
+++ 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java
@@ -22,6 +22,7 @@ import java.util.function.Function;
 
 import org.kie.kogito.codegen.api.Generator;
 import org.kie.kogito.codegen.api.context.KogitoBuildContext;
+import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -65,9 +66,14 @@ public final class CodegenUtil {
      * @see CodegenUtil#getProperty
      */
     public static boolean isTransactionEnabled(Generator generator, 
KogitoBuildContext context) {
-        boolean propertyValue = getProperty(generator, context, 
TRANSACTION_ENABLED, Boolean::parseBoolean, true);
-        LOG.debug("trying to compute property {} for generator {} property 
with value {}", TRANSACTION_ENABLED, generator.name(), propertyValue);
-        return propertyValue;
+        return isTransactionEnabled(generator, context, true);
+    }
+
+    public static boolean isTransactionEnabled(Generator generator, 
KogitoBuildContext context, boolean defaultValue) {
+        boolean propertyValue = getProperty(generator, context, 
TRANSACTION_ENABLED, Boolean::parseBoolean, defaultValue);
+        LOG.debug("Compute property {} for generator {} property with value 
{}", TRANSACTION_ENABLED, generator.name(), propertyValue);
+        // java implementation does not have transactions
+        return !JavaKogitoBuildContext.CONTEXT_NAME.equals(context.name()) && 
propertyValue;
     }
 
     /**
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java
 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java
new file mode 100644
index 0000000000..3230f0d71b
--- /dev/null
+++ 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java
@@ -0,0 +1,77 @@
+/*
+ * 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.quarkus.workflow.handler;
+
+import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
+import org.kie.kogito.Model;
+import org.kie.kogito.handler.ExceptionHandler;
+import org.kie.kogito.process.MutableProcessInstances;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.process.impl.AbstractProcessInstance;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.uow.UnitOfWorkManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Instance;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.transaction.Transactional.TxType;
+
+@ApplicationScoped
+public class ExceptionHandlerTransaction implements ExceptionHandler {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ExceptionHandlerTransaction.class);
+
+    @Inject
+    UnitOfWorkManager unitOfWorkManager;
+
+    @Inject
+    Instance<Processes> processesContainer;
+
+    @Override
+    @Transactional(value = TxType.REQUIRES_NEW)
+    public void handle(Exception th) {
+        if (processesContainer.isResolvable()) {
+            return;
+        }
+
+        Processes processes = processesContainer.get();
+        if (th instanceof ProcessInstanceExecutionException) {
+            ProcessInstanceExecutionException 
processInstanceExecutionException = (ProcessInstanceExecutionException) th;
+            LOG.info("handling exception {} by the handler {}", th, 
this.getClass().getName());
+            UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> {
+                String processInstanceId = 
processInstanceExecutionException.getProcessInstanceId();
+                
processes.processByProcessInstanceId(processInstanceId).ifPresent(processDefinition
 -> {
+                    
processDefinition.instances().findById(processInstanceId).ifPresent(instance -> 
{
+                        AbstractProcessInstance<? extends Model> 
processInstance = ((AbstractProcessInstance<? extends Model>) instance);
+                        ((WorkflowProcessInstanceImpl) 
processInstance.internalGetProcessInstance()).internalSetError(processInstanceExecutionException);
+                        ((MutableProcessInstances) 
processDefinition.instances()).update(processInstanceId, processInstance);
+                    });
+
+                });
+
+                return null;
+            });
+        }
+    }
+
+}
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java
 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java
new file mode 100644
index 0000000000..d9996d62f1
--- /dev/null
+++ 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.process.handler;
+
+import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
+import org.kie.kogito.Model;
+import org.kie.kogito.handler.ExceptionHandler;
+import org.kie.kogito.process.MutableProcessInstances;
+import org.kie.kogito.process.ProcessInstanceExecutionException;
+import org.kie.kogito.process.Processes;
+import org.kie.kogito.process.impl.AbstractProcessInstance;
+import org.kie.kogito.services.uow.UnitOfWorkExecutor;
+import org.kie.kogito.uow.UnitOfWorkManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ExceptionHandlerTransaction implements ExceptionHandler {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ExceptionHandlerTransaction.class);
+
+    @Autowired
+    UnitOfWorkManager unitOfWorkManager;
+
+    @Autowired(required = false)
+    Processes processes;
+
+    @Override
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void handle(Exception th) {
+        if (processes == null) {
+            return;
+        }
+        if (th instanceof ProcessInstanceExecutionException) {
+            ProcessInstanceExecutionException 
processInstanceExecutionException = (ProcessInstanceExecutionException) th;
+            LOG.info("handling exception {} by the handler {}", th, 
this.getClass().getName());
+            UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> {
+                String processInstanceId = 
processInstanceExecutionException.getProcessInstanceId();
+                
processes.processByProcessInstanceId(processInstanceId).ifPresent(processDefinition
 -> {
+                    
processDefinition.instances().findById(processInstanceId).ifPresent(instance -> 
{
+                        AbstractProcessInstance<? extends Model> 
processInstance = ((AbstractProcessInstance<? extends Model>) instance);
+                        ((WorkflowProcessInstanceImpl) 
processInstance.internalGetProcessInstance()).internalSetError(processInstanceExecutionException);
+                        ((MutableProcessInstances) 
processDefinition.instances()).update(processInstanceId, processInstance);
+                    });
+
+                });
+
+                return null;
+            });
+        }
+    }
+
+}
diff --git a/quarkus/addons/rest-exception-handler/pom.xml 
b/quarkus/addons/rest-exception-handler/pom.xml
index a5419789ab..03f28e9266 100644
--- a/quarkus/addons/rest-exception-handler/pom.xml
+++ b/quarkus/addons/rest-exception-handler/pom.xml
@@ -51,6 +51,10 @@
       <artifactId>jakarta.inject-api</artifactId>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>jakarta.enterprise</groupId>
+      <artifactId>jakarta.enterprise.cdi-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-junit-jupiter</artifactId>
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java
index 21b70e76d7..09a44058b3 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java
@@ -23,13 +23,6 @@ import jakarta.ws.rs.ext.ExceptionMapper;
 
 public abstract class BaseExceptionMapper<E extends Throwable> implements 
ExceptionMapper<E> {
 
-    protected ExceptionsHandler exceptionsHandler;
-
-    protected BaseExceptionMapper() {
-        this.exceptionsHandler = new ExceptionsHandler();
-    }
-
     @Override
-    @SuppressWarnings("squid:S3038")
     public abstract Response toResponse(E e);
 }
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java
index ec1bd6679d..2d1afaf578 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java
@@ -18,54 +18,60 @@
  */
 package org.kie.kogito.resource.exceptions;
 
+import org.kie.kogito.handler.ExceptionHandler;
+
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 
-public class ExceptionsHandler extends BaseExceptionsHandler<Response> {
+public class ExceptionsHandler extends AbstractExceptionsHandler<Response> {
+
+    public ExceptionsHandler(Iterable<ExceptionHandler> handlers) {
+        super(handlers);
+    }
 
     @Override
-    protected <R> Response badRequest(R body) {
+    protected Response badRequest(ExceptionBodyMessage body) {
         return Response
                 .status(Response.Status.BAD_REQUEST)
                 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .entity(body)
+                .entity(body.getBody())
                 .build();
     }
 
     @Override
-    protected <R> Response conflict(R body) {
+    protected Response conflict(ExceptionBodyMessage body) {
         return Response
                 .status(Response.Status.CONFLICT)
                 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .entity(body)
+                .entity(body.getBody())
                 .build();
     }
 
     @Override
-    protected <R> Response internalError(R body) {
+    protected Response internalError(ExceptionBodyMessage body) {
         return Response
                 .status(Response.Status.INTERNAL_SERVER_ERROR)
                 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .entity(body)
+                .entity(body.getBody())
                 .build();
     }
 
     @Override
-    protected <R> Response notFound(R body) {
+    protected Response notFound(ExceptionBodyMessage body) {
         return Response
                 .status(Response.Status.NOT_FOUND)
                 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .entity(body)
+                .entity(body.getBody())
                 .build();
     }
 
     @Override
-    protected <R> Response forbidden(R body) {
+    protected Response forbidden(ExceptionBodyMessage body) {
         return Response
                 .status(Response.Status.FORBIDDEN)
                 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
-                .entity(body)
+                .entity(body.getBody())
                 .build();
     }
 }
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java
similarity index 72%
copy from 
quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
copy to 
quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java
index 56a734a511..a207c68072 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java
@@ -18,14 +18,15 @@
  */
 package org.kie.kogito.resource.exceptions;
 
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.ext.Provider;
+import org.kie.kogito.handler.ExceptionHandler;
 
-@Provider
-public class IllegalArgumentExceptionMapper extends 
BaseExceptionMapper<IllegalArgumentException> {
+import jakarta.enterprise.inject.Instance;
+import jakarta.enterprise.inject.Produces;
 
-    @Override
-    public Response toResponse(IllegalArgumentException e) {
-        return exceptionsHandler.mapException(e);
+public class ExceptionsHandlerProducer {
+
+    @Produces
+    public ExceptionsHandler newExceptionsHandler(Instance<ExceptionHandler> 
handlers) {
+        return new ExceptionsHandler(handlers);
     }
 }
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
index 56a734a511..e019fd12f9 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java
@@ -18,12 +18,16 @@
  */
 package org.kie.kogito.resource.exceptions;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class IllegalArgumentExceptionMapper extends 
BaseExceptionMapper<IllegalArgumentException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(IllegalArgumentException e) {
         return exceptionsHandler.mapException(e);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java
index 82341b6eec..7d5b5a938a 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class InvalidLifeCyclePhaseExceptionMapper extends 
BaseExceptionMapper<InvalidLifeCyclePhaseException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(InvalidLifeCyclePhaseException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java
index ff04b15e15..abd80e367d 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.internal.process.workitem.InvalidTransitionException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class InvalidTransitionExceptionMapper extends 
BaseExceptionMapper<InvalidTransitionException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(InvalidTransitionException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java
index 698a1155e7..891c60fb7c 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.NodeInstanceNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class NodeInstanceNotFoundExceptionMapper extends 
BaseExceptionMapper<NodeInstanceNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(NodeInstanceNotFoundException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java
index 182e1df839..c41b0397fe 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.NodeNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class NodeNotFoundExceptionMapper extends 
BaseExceptionMapper<NodeNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(NodeNotFoundException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java
index 030783daa6..2831677feb 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class NotAuthorizedExceptionMapper extends 
BaseExceptionMapper<NotAuthorizedException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(NotAuthorizedException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java
index ed9bb35c96..6c54bf486e 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.ProcessInstanceDuplicatedException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class ProcessInstanceDuplicatedExceptionMapper extends 
BaseExceptionMapper<ProcessInstanceDuplicatedException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(ProcessInstanceDuplicatedException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java
index 9e3f41ef95..3ff0d6449c 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.ProcessInstanceExecutionException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class ProcessInstanceExecutionExceptionMapper extends 
BaseExceptionMapper<ProcessInstanceExecutionException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(ProcessInstanceExecutionException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java
index 46b511d6b3..d0e31c3fc5 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.ProcessInstanceNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class ProcessInstanceNotFoundExceptionMapper extends 
BaseExceptionMapper<ProcessInstanceNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(ProcessInstanceNotFoundException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java
index 9871032881..be350a1d6d 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.usertask.UserTaskInstanceNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class UserTaskInstanceNotAuthorizedExceptionMapper extends 
BaseExceptionMapper<UserTaskInstanceNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(UserTaskInstanceNotFoundException e) {
         return exceptionsHandler.mapException(e);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java
index d74ddf1fc5..196bd85535 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class UserTaskInstanceNotFoundExceptionMapper extends 
BaseExceptionMapper<UserTaskInstanceNotAuthorizedException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(UserTaskInstanceNotAuthorizedException e) {
         return exceptionsHandler.mapException(e);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java
index 233d3cd679..063439ead6 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.usertask.UserTaskInstanceNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class UserTaskTransitionExceptionMapper extends 
BaseExceptionMapper<UserTaskInstanceNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(UserTaskInstanceNotFoundException e) {
         return exceptionsHandler.mapException(e);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java
index 85ab766ee4..fdea325335 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.process.VariableViolationException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class VariableViolationExceptionMapper extends 
BaseExceptionMapper<VariableViolationException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(VariableViolationException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java
index 1ffb619ceb..bcd9f18ede 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class WorkItemExecutionExceptionMapper extends 
BaseExceptionMapper<WorkItemExecutionException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(WorkItemExecutionException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java
 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java
index a0d0731dcc..000e0f748a 100644
--- 
a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java
+++ 
b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java
@@ -20,12 +20,16 @@ package org.kie.kogito.resource.exceptions;
 
 import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException;
 
+import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 @Provider
 public class WorkItemNotFoundExceptionMapper extends 
BaseExceptionMapper<WorkItemNotFoundException> {
 
+    @Inject
+    ExceptionsHandler exceptionsHandler;
+
     @Override
     public Response toResponse(WorkItemNotFoundException exception) {
         return exceptionsHandler.mapException(exception);
diff --git 
a/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java
 
b/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java
index 028a91a91b..156d8aae68 100644
--- 
a/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java
+++ 
b/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java
@@ -18,6 +18,8 @@
  */
 package org.kie.kogito.resource.exceptions;
 
+import java.util.ArrayList;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -40,7 +42,7 @@ class ExceptionsHandlerTest {
     private ExceptionsHandler tested;
 
     @Mock
-    private Object body;
+    private ExceptionBodyMessage body;
 
     @Mock
     private RuntimeDelegate runtimeDelegate;
@@ -53,7 +55,7 @@ class ExceptionsHandlerTest {
 
     @BeforeEach
     void setUp() {
-        tested = new ExceptionsHandler();
+        tested = new ExceptionsHandler(new ArrayList<>());
         RuntimeDelegate.setInstance(runtimeDelegate);
         when(runtimeDelegate.createResponseBuilder()).thenReturn(builder);
         
when(builder.status(any(Response.StatusType.class))).thenReturn(builder);
@@ -71,7 +73,7 @@ class ExceptionsHandlerTest {
     private void assertRequest(Response.Status status) {
         verify(builder).header(HttpHeaders.CONTENT_TYPE, 
MediaType.APPLICATION_JSON);
         verify(builder).status((Response.StatusType) status);
-        verify(builder).entity(body);
+        verify(builder).entity(body.getBody());
     }
 
     @Test
diff --git 
a/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java
 
b/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java
index 6165d773ab..29ca134938 100644
--- 
a/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java
+++ 
b/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java
@@ -19,11 +19,10 @@
 package org.kie.kogito.addon.source.files;
 
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
+import java.util.Optional;
 
-import org.kie.kogito.resource.exceptions.ExceptionsHandler;
 import org.kie.kogito.source.files.SourceFile;
 import org.kie.kogito.source.files.SourceFilesProvider;
 
@@ -41,24 +40,24 @@ import jakarta.ws.rs.core.Response;
 @Path("/management/processes/")
 public final class SourceFilesResource {
 
-    private static final ExceptionsHandler EXCEPTIONS_HANDLER = new 
ExceptionsHandler();
-
     SourceFilesProvider sourceFilesProvider;
 
     @GET
     @Path("sources")
     @Produces(MediaType.APPLICATION_OCTET_STREAM)
-    public Response getSourceFileByUri(@QueryParam("uri") String uri) {
-        return sourceFilesProvider.getSourceFilesByUri(uri)
-                .map(sourceFile -> {
-                    try (InputStream file = new 
ByteArrayInputStream(sourceFile.readContents())) {
-                        return Response.ok(file, 
MediaType.APPLICATION_OCTET_STREAM)
-                                .header("Content-Disposition", "inline; 
filename=\"" + java.nio.file.Path.of(sourceFile.getUri()).getFileName() + "\"")
-                                .build();
-                    } catch (Exception e) {
-                        return EXCEPTIONS_HANDLER.mapException(e);
-                    }
-                }).orElseGet(() -> 
Response.status(Response.Status.NOT_FOUND).build());
+    public Response getSourceFileByUri(@QueryParam("uri") String uri) throws 
Exception {
+        Optional<SourceFile> sourceFile = 
sourceFilesProvider.getSourceFilesByUri(uri);
+
+        if (sourceFile.isEmpty()) {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+
+        try (InputStream file = new 
ByteArrayInputStream(sourceFile.get().readContents())) {
+            return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
+                    .header("Content-Disposition", "inline; filename=\"" + 
java.nio.file.Path.of(sourceFile.get().getUri()).getFileName() + "\"")
+                    .build();
+        }
+
     }
 
     @GET
@@ -71,15 +70,15 @@ public final class SourceFilesResource {
     @GET
     @Path("{processId}/source")
     @Produces(MediaType.TEXT_PLAIN)
-    public Response getSourceFileByProcessId(@PathParam("processId") String 
processId) {
-        return sourceFilesProvider.getProcessSourceFile(processId)
-                .map(sourceFile -> {
-                    try {
-                        return Response.ok(sourceFile.readContents()).build();
-                    } catch (IOException e) {
-                        return EXCEPTIONS_HANDLER.mapException(e);
-                    }
-                }).orElseGet(() -> 
Response.status(Response.Status.NOT_FOUND).build());
+    public Response getSourceFileByProcessId(@PathParam("processId") String 
processId) throws Exception {
+        Optional<SourceFile> sourceFile = 
sourceFilesProvider.getProcessSourceFile(processId);
+
+        if (sourceFile.isEmpty()) {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+
+        return Response.ok(sourceFile.get().readContents()).build();
+
     }
 
     @Inject
diff --git 
a/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java
 
b/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java
index 642bf95be8..2968fe78d6 100644
--- 
a/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java
+++ 
b/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java
@@ -58,14 +58,14 @@ class SourceFilesResourceTest {
     }
 
     @Test
-    void getEmptySourceFileByProcessIdTest() {
+    void getEmptySourceFileByProcessIdTest() throws Exception {
         
when(mockSourceFileProvider.getProcessSourceFile(PROCESS_ID)).thenReturn(Optional.empty());
         
assertThat(sourceFilesTestResource.getSourceFileByProcessId(PROCESS_ID).getStatus()).isEqualTo(Response.Status.NOT_FOUND.getStatusCode());
         verify(mockSourceFileProvider).getProcessSourceFile(PROCESS_ID);
     }
 
     @Test
-    void getValidSourceFileByProcessIdTest() {
+    void getValidSourceFileByProcessIdTest() throws Exception {
         
when(mockSourceFileProvider.getProcessSourceFile(PROCESS_ID)).thenReturn(Optional.of(new
 SourceFile("petstore.sw.json")));
         
assertThat(sourceFilesTestResource.getSourceFileByProcessId(PROCESS_ID).getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
         verify(mockSourceFileProvider).getProcessSourceFile(PROCESS_ID);
diff --git 
a/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java
 
b/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java
index d5d5dfa96e..9872513141 100644
--- 
a/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java
+++ 
b/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java
@@ -18,6 +18,9 @@
  */
 package org.kie.kogito.resource.exceptions.springboot;
 
+import java.util.List;
+import java.util.Map;
+
 import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException;
 import org.kie.kogito.internal.process.workitem.InvalidTransitionException;
 import org.kie.kogito.internal.process.workitem.NotAuthorizedException;
@@ -29,7 +32,9 @@ import 
org.kie.kogito.process.ProcessInstanceDuplicatedException;
 import org.kie.kogito.process.ProcessInstanceExecutionException;
 import org.kie.kogito.process.ProcessInstanceNotFoundException;
 import org.kie.kogito.process.VariableViolationException;
-import org.kie.kogito.resource.exceptions.BaseExceptionsHandler;
+import org.kie.kogito.resource.exceptions.AbstractExceptionsHandler;
+import org.kie.kogito.resource.exceptions.ExceptionBodyMessage;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
@@ -37,105 +42,111 @@ import 
org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 
 @ControllerAdvice
-public class ExceptionsHandler extends BaseExceptionsHandler<ResponseEntity> {
+public class ExceptionsHandler extends 
AbstractExceptionsHandler<ResponseEntity<Map<String, String>>> {
+
+    @Autowired
+    public ExceptionsHandler(List<org.kie.kogito.handler.ExceptionHandler> 
handlers) {
+        super(handlers);
+    }
 
     @Override
-    protected <R> ResponseEntity badRequest(R body) {
+    protected ResponseEntity<Map<String, String>> 
badRequest(ExceptionBodyMessage body) {
         return ResponseEntity
                 .badRequest()
                 .contentType(MediaType.APPLICATION_JSON)
-                .body(body);
+                .body(body.getBody());
     }
 
     @Override
-    protected <R> ResponseEntity conflict(R body) {
+    protected ResponseEntity<Map<String, String>> 
conflict(ExceptionBodyMessage body) {
         return ResponseEntity
                 .status(HttpStatus.CONFLICT)
                 .contentType(MediaType.APPLICATION_JSON)
-                .body(body);
+                .body(body.getBody());
     }
 
     @Override
-    protected <R> ResponseEntity internalError(R body) {
+    protected ResponseEntity<Map<String, String>> 
internalError(ExceptionBodyMessage body) {
         return ResponseEntity
                 .status(HttpStatus.INTERNAL_SERVER_ERROR)
                 .contentType(MediaType.APPLICATION_JSON)
-                .body(body);
+                .body(body.getBody());
     }
 
     @Override
-    protected <R> ResponseEntity notFound(R body) {
+    protected ResponseEntity<Map<String, String>> 
notFound(ExceptionBodyMessage body) {
         return ResponseEntity
                 .status(HttpStatus.NOT_FOUND)
                 .contentType(MediaType.APPLICATION_JSON)
-                .body(body);
+                .body(body.getBody());
     }
 
     @Override
-    protected <R> ResponseEntity forbidden(R body) {
+    protected ResponseEntity<Map<String, String>> 
forbidden(ExceptionBodyMessage body) {
         return ResponseEntity
                 .status(HttpStatus.FORBIDDEN)
                 .contentType(MediaType.APPLICATION_JSON)
-                .body(body);
+                .body(body.getBody());
     }
 
     @ExceptionHandler(InvalidLifeCyclePhaseException.class)
-    public ResponseEntity toResponse(InvalidLifeCyclePhaseException exception) 
{
+    public ResponseEntity<Map<String, String>> 
toResponse(InvalidLifeCyclePhaseException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(InvalidTransitionException.class)
-    public ResponseEntity toResponse(InvalidTransitionException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(InvalidTransitionException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(NodeInstanceNotFoundException.class)
-    public ResponseEntity toResponse(NodeInstanceNotFoundException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(NodeInstanceNotFoundException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(NodeNotFoundException.class)
-    public ResponseEntity toResponse(NodeNotFoundException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(NodeNotFoundException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(NotAuthorizedException.class)
-    public ResponseEntity toResponse(NotAuthorizedException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(NotAuthorizedException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(ProcessInstanceDuplicatedException.class)
-    public ResponseEntity<Object> 
toResponse(ProcessInstanceDuplicatedException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(ProcessInstanceDuplicatedException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(ProcessInstanceExecutionException.class)
-    public ResponseEntity toResponse(ProcessInstanceExecutionException 
exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(ProcessInstanceExecutionException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(ProcessInstanceNotFoundException.class)
-    public ResponseEntity toResponse(ProcessInstanceNotFoundException 
exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(ProcessInstanceNotFoundException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(WorkItemNotFoundException.class)
-    public ResponseEntity toResponse(WorkItemNotFoundException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(WorkItemNotFoundException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(WorkItemExecutionException.class)
-    public ResponseEntity toResponse(WorkItemExecutionException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(WorkItemExecutionException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(VariableViolationException.class)
-    public ResponseEntity toResponse(VariableViolationException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(VariableViolationException exception) {
         return mapException(exception);
     }
 
     @ExceptionHandler(IllegalArgumentException.class)
-    public ResponseEntity toResponse(IllegalArgumentException exception) {
+    public ResponseEntity<Map<String, String>> 
toResponse(IllegalArgumentException exception) {
         return mapException(exception);
     }
+
 }
diff --git 
a/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java
 
b/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java
index 4512a30b7f..10c8290620 100644
--- 
a/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java
+++ 
b/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java
@@ -18,6 +18,9 @@
  */
 package org.kie.kogito.resource.exceptions.springboot;
 
+import java.util.ArrayList;
+import java.util.Map;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -29,6 +32,7 @@ import 
org.kie.kogito.process.ProcessInstanceDuplicatedException;
 import org.kie.kogito.process.ProcessInstanceExecutionException;
 import org.kie.kogito.process.ProcessInstanceNotFoundException;
 import org.kie.kogito.process.VariableViolationException;
+import org.kie.kogito.resource.exceptions.ExceptionBodyMessage;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.http.HttpStatus;
@@ -44,45 +48,45 @@ class ExceptionsHandlerTest {
     private ExceptionsHandler tested;
 
     @Mock
-    private Object body;
+    private ExceptionBodyMessage body;
 
     @BeforeEach
     void setUp() {
-        tested = spy(new ExceptionsHandler());
+        tested = spy(new ExceptionsHandler(new ArrayList<>()));
     }
 
     @Test
     void testBadRequest() {
-        ResponseEntity responseEntity = tested.badRequest(body);
+        ResponseEntity<Map<String, String>> responseEntity = 
tested.badRequest(body);
         assertResponse(responseEntity, HttpStatus.BAD_REQUEST);
     }
 
-    private void assertResponse(ResponseEntity responseEntity, HttpStatus 
status) {
+    private void assertResponse(ResponseEntity<Map<String, String>> 
responseEntity, HttpStatus status) {
         assertThat(responseEntity.getStatusCode()).isEqualTo(status);
-        assertThat(responseEntity.getBody()).isEqualTo(body);
+        assertThat(responseEntity.getBody()).isEqualTo(body.getBody());
     }
 
     @Test
     void testConflict() {
-        ResponseEntity responseEntity = tested.conflict(body);
+        ResponseEntity<Map<String, String>> responseEntity = 
tested.conflict(body);
         assertResponse(responseEntity, HttpStatus.CONFLICT);
     }
 
     @Test
     void testIternalError() {
-        ResponseEntity responseEntity = tested.internalError(body);
+        ResponseEntity<Map<String, String>> responseEntity = 
tested.internalError(body);
         assertResponse(responseEntity, HttpStatus.INTERNAL_SERVER_ERROR);
     }
 
     @Test
     void testNotFound() {
-        ResponseEntity responseEntity = tested.badRequest(body);
+        ResponseEntity<Map<String, String>> responseEntity = 
tested.badRequest(body);
         assertResponse(responseEntity, HttpStatus.BAD_REQUEST);
     }
 
     @Test
     void testForbidden() {
-        ResponseEntity responseEntity = tested.forbidden(body);
+        ResponseEntity<Map<String, String>> responseEntity = 
tested.forbidden(body);
         assertResponse(responseEntity, HttpStatus.FORBIDDEN);
     }
 
diff --git 
a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
 
b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
index b823c16d26..d1875057a9 100644
--- 
a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
+++ 
b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java
@@ -22,6 +22,9 @@ import org.kie.kogito.config.ConfigBean;
 import org.kie.kogito.correlation.CorrelationService;
 import org.kie.kogito.event.correlation.DefaultCorrelationService;
 import org.kie.kogito.process.version.ProjectVersionProcessVersionResolver;
+import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
+import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
+import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kogito.workitem.rest.RestWorkItemHandlerUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -53,6 +56,12 @@ public class KogitoBeanProducer {
         return new 
ProjectVersionProcessVersionResolver(configBean.getGav().orElseThrow(() -> new 
RuntimeException("Unable to use kogito.workflow.version-strategy without a 
project GAV")));
     }
 
+    @Bean
+    @ConditionalOnMissingBean(UnitOfWorkManager.class)
+    UnitOfWorkManager unitOfWorkManager() {
+        return new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory());
+    }
+
     @Bean
     @ConditionalOnMissingBean(WebClientOptions.class)
     WebClientOptions sslDefaultOptions() {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to