diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiConfiguration.java
new file mode 100644
index 00000000000..eed32fcfd80
--- /dev/null
+++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiConfiguration.java
@@ -0,0 +1,16 @@
+package com.netgrif.application.engine.actions;
+
+import com.netgrif.application.engine.adapter.spring.actions.ActionApi;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ActionApiConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(ActionApi.class)
+    public ActionApi actionApi() {
+        return new ActionApiImpl();
+    }
+}
diff --git a/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java
new file mode 100644
index 00000000000..c54221a285c
--- /dev/null
+++ b/application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java
@@ -0,0 +1,194 @@
+package com.netgrif.application.engine.actions;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.netgrif.application.engine.adapter.spring.actions.ActionApi;
+import com.netgrif.application.engine.auth.service.UserService;
+import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService;
+import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService;
+import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest;
+import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest;
+import com.netgrif.application.engine.objects.auth.domain.AbstractUser;
+import com.netgrif.application.engine.objects.auth.domain.ActorTransformer;
+import com.netgrif.application.engine.objects.auth.domain.LoggedUser;
+import com.netgrif.application.engine.objects.auth.domain.User;
+import com.netgrif.application.engine.objects.auth.dto.AuthPrincipalDto;
+import com.netgrif.application.engine.objects.petrinet.domain.throwable.TransitionNotExecutableException;
+import com.netgrif.application.engine.objects.workflow.domain.Case;
+import com.netgrif.application.engine.objects.workflow.domain.Task;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.DeleteCaseEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.AssignTaskEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.CancelTaskEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.FinishTaskEventOutcome;
+import com.netgrif.application.engine.workflow.service.interfaces.IDataService;
+import com.netgrif.application.engine.workflow.service.interfaces.ITaskService;
+import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService;
+import com.querydsl.core.types.Predicate;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.*;
+
+@Slf4j
+public class ActionApiImpl implements ActionApi {
+
+    private UserService userService;
+
+    private IDataService dataService;
+
+    private ITaskService taskService;
+
+    private IWorkflowService workflowService;
+
+    private IElasticCaseService elasticCaseService;
+
+    private IElasticTaskService elasticTaskService;
+
+    @Autowired
+    public void setDataService(IDataService dataService) {
+        this.dataService = dataService;
+    }
+
+    @Autowired
+    public void setTaskService(ITaskService taskService) {
+        this.taskService = taskService;
+    }
+
+    @Autowired
+    public void setWorkflowService(IWorkflowService workflowService) {
+        this.workflowService = workflowService;
+    }
+
+    @Autowired
+    public void setElasticCaseService(IElasticCaseService elasticCaseService) {
+        this.elasticCaseService = elasticCaseService;
+    }
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @Autowired
+    public void setElasticTaskService(IElasticTaskService elasticTaskService) {
+        this.elasticTaskService = elasticTaskService;
+    }
+
+    @Override
+    public GetDataEventOutcome getData(String taskId, Map<String, String> params) {
+        return dataService.getData(taskId, params);
+    }
+
+    @Override
+    public SetDataEventOutcome setData(String taskId, Map<String, Map<String, String>> dataSet, Map<String, String> params) throws JsonProcessingException {
+        ObjectMapper mapper = new ObjectMapper(); 
+        String json = mapper.writeValueAsString(dataSet);
+        ObjectNode values = (ObjectNode) mapper.readTree(json);
+        return dataService.setData(taskId, values, params);
+    }
+
+    @Override
+    public Page<Case> searchCases(String processIdentifier, Predicate predicate, Pageable pageable) {
+        return workflowService.search(predicate, pageable);
+    }
+
+    @Override
+    public Page<Case> searchCases(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection) {
+        boolean intersect = Boolean.TRUE.equals(isIntersection);
+        List<CaseSearchRequest> caseSearchRequests = elasticStringQueries.stream().map(query -> CaseSearchRequest.builder().query(query).build()).toList();
+        LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto));
+        Locale locale = LocaleContextHolder.getLocale();
+        return elasticCaseService.search(caseSearchRequests, loggedUser, pageable, locale, intersect);
+    }
+
+    @Override
+    public Long countCases(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Boolean isIntersection) {
+        boolean intersect = Boolean.TRUE.equals(isIntersection);
+        List<CaseSearchRequest> caseSearchRequests = elasticStringQueries.stream().map(query -> CaseSearchRequest.builder().query(query).build()).toList();
+        LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto));
+        Locale locale = LocaleContextHolder.getLocale();
+        return elasticCaseService.count(caseSearchRequests, loggedUser, locale, intersect);
+    }
+
+    @Override
+    public CreateCaseEventOutcome createCaseByIdentifier(String identifier, String title, String color, AuthPrincipalDto authPrincipalDto, Map<String, String> params) {
+        LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto));
+        Locale locale = LocaleContextHolder.getLocale();
+        return workflowService.createCaseByIdentifier(identifier, title, color, loggedUser, locale, params);
+    }
+
+    @Override
+    public DeleteCaseEventOutcome deleteCase(String caseId, Map<String, String> params) {
+        return workflowService.deleteCase(caseId, params);
+    }
+
+    @Override
+    public Page<Task> searchTasks(String processIdentifier, Predicate predicate, Pageable pageable) {
+        return taskService.search(predicate, pageable);
+    }
+
+    @Override
+    public Page<Task> searchTasks(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection) {
+        boolean intersect = Boolean.TRUE.equals(isIntersection);
+        List<ElasticTaskSearchRequest> taskSearchRequests = elasticStringQueries.stream().map(query -> ElasticTaskSearchRequest.builder().query(query).build()).toList();
+        LoggedUser loggedUser = ActorTransformer.toLoggedUser(resolveAbstractUser(authPrincipalDto));
+        Locale locale = LocaleContextHolder.getLocale();
+        return elasticTaskService.search(taskSearchRequests, loggedUser, pageable, locale, intersect);
+    }
+
+    @Override
+    public AssignTaskEventOutcome assignTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params) throws TransitionNotExecutableException {
+        Task task = taskService.findOne(taskId);
+        AbstractUser user = resolveAbstractUser(authPrincipalDto);
+        return taskService.assignTask(task, user, params);
+    }
+
+    @Override
+    public CancelTaskEventOutcome cancelTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params) {
+        Task task = taskService.findOne(taskId);
+        AbstractUser user = resolveAbstractUser(authPrincipalDto);
+        return taskService.cancelTask(task, user, params);
+    }
+
+    @Override
+    public FinishTaskEventOutcome finishTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params) throws TransitionNotExecutableException {
+        Task task = taskService.findOne(taskId);
+        AbstractUser user = resolveAbstractUser(authPrincipalDto);
+        return taskService.finishTask(task, user, params);
+    }
+
+    @Override
+    public Case findCase(String caseId) {
+        return workflowService.findOne(caseId);
+    }
+
+    @Override
+    public Task findTask(String taskId) {
+        return taskService.findOne(taskId);
+    }
+
+    @Override
+    public Page<User> searchUsers(Predicate predicate, Pageable pageable, String realmId) {
+        return userService.search(predicate, pageable, realmId);
+    }
+
+    @Override
+    public AbstractUser getSystemUser() {
+        return userService.getSystem();
+    }
+
+    private AbstractUser resolveAbstractUser(AuthPrincipalDto authPrincipalDto) {
+        if (authPrincipalDto == null) {
+            throw new IllegalArgumentException("AuthPrincipalDto cannot be null.");
+        }
+        Optional<AbstractUser> userOptional = userService.findUserByUsername(authPrincipalDto.getUsername(), authPrincipalDto.getRealmId());
+        return userOptional.orElseThrow(() -> new IllegalArgumentException("User with username [%s] and realm ID [%s] not found".formatted(authPrincipalDto.getUsername(), authPrincipalDto.getRealmId())));
+    }
+}
diff --git a/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java
index a01e639ae7f..f30bd911551 100644
--- a/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java
+++ b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java
@@ -4,12 +4,14 @@
 import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.PetriNet;
 import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.TaskSearchCaseRequest;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.NoArgsConstructor;
 
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+@Builder
 @NoArgsConstructor
 @AllArgsConstructor
 public class ElasticTaskSearchRequest extends TaskSearchRequest {
diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java
index 1c94340d3a8..1da1afa0b03 100644
--- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java
+++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java
@@ -15,8 +15,6 @@
 import com.netgrif.application.engine.objects.importer.model.EventPhaseType;
 import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
 import com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action;
-import com.netgrif.application.engine.objects.workflow.domain.Case;
-import com.netgrif.application.engine.objects.workflow.domain.QCase;
 import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.context.RoleContext;
 import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.runner.RoleActionsRunner;
 import com.netgrif.application.engine.objects.petrinet.domain.events.Event;
@@ -102,8 +100,8 @@ public Optional<ProcessRole> get(ProcessResourceId processResourceId) {
     }
 
     @Override
-    public void delete(String s) {
-        Optional<ProcessRole> processRole = processRoleRepository.findByCompositeId(s);
+    public void delete(String roleId) {
+        Optional<ProcessRole> processRole = processRoleRepository.findByCompositeId(roleId);
         processRole.ifPresent(processRoleRepository::delete);
     }
 
diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/AuthPrincipalDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/AuthPrincipalDto.java
new file mode 100644
index 00000000000..3333122aae3
--- /dev/null
+++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/AuthPrincipalDto.java
@@ -0,0 +1,25 @@
+package com.netgrif.application.engine.objects.auth.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+public class AuthPrincipalDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 6725518942728316525L;
+
+    private String username;
+
+    private String realmId;
+
+    @ToString.Exclude
+    @EqualsAndHashCode.Exclude
+    @JsonIgnore
+    private String sessionId;
+}
diff --git a/nae-spring-core-adapter/pom.xml b/nae-spring-core-adapter/pom.xml
index baa42182723..e3edd065782 100644
--- a/nae-spring-core-adapter/pom.xml
+++ b/nae-spring-core-adapter/pom.xml
@@ -98,14 +98,19 @@
             <artifactId>jackson-module-jsonSchema</artifactId>
             <version>${jackson.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.querydsl</groupId>
             <artifactId>querydsl-core</artifactId>
             <version>${querydsl.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
+            <groupId>com.querydsl</groupId>
+            <artifactId>querydsl-apt</artifactId>
+            <version>${querydsl.version}</version>
         </dependency>
     </dependencies>
 
@@ -131,6 +136,30 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>com.mysema.maven</groupId>
+                <artifactId>apt-maven-plugin</artifactId>
+                <version>1.1.3</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>target/generated-sources/java</outputDirectory>
+                            <processors>
+                                <processor>
+                                    com.querydsl.apt.QuerydslAnnotationProcessor
+                                </processor>
+                            </processors>
+                            <options>
+                                <querydsl.excludedClasses>groovy.lang.MetaClass</querydsl.excludedClasses>
+                                <querydsl.unknownAsEmbeddable>true</querydsl.unknownAsEmbeddable>
+                            </options>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java
new file mode 100644
index 00000000000..b2e112a6b17
--- /dev/null
+++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApi.java
@@ -0,0 +1,198 @@
+package com.netgrif.application.engine.adapter.spring.actions;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.netgrif.application.engine.objects.auth.domain.AbstractUser;
+import com.netgrif.application.engine.objects.auth.domain.User;
+import com.netgrif.application.engine.objects.auth.dto.AuthPrincipalDto;
+import com.netgrif.application.engine.objects.petrinet.domain.throwable.TransitionNotExecutableException;
+import com.netgrif.application.engine.objects.workflow.domain.Case;
+import com.netgrif.application.engine.objects.workflow.domain.Task;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.DeleteCaseEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.AssignTaskEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.CancelTaskEventOutcome;
+import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.taskoutcomes.FinishTaskEventOutcome;
+import com.querydsl.core.types.Predicate;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.Map;
+import java.util.List;
+
+
+/**
+ * ActionApi provides methods for performing operations and actions on workflow-related entities such as cases,
+ * tasks, and users. It facilitates data retrieval, task management, and realm-specific searches within the system.
+ */
+public interface ActionApi {
+
+    /**
+     * Retrieves data associated with a specific task.
+     *
+     * @param taskId the ID of the task for which data is to be retrieved
+     * @param params additional parameters for data retrieval
+     * @return the outcome of the data retrieval operation
+     */
+    GetDataEventOutcome getData(String taskId, Map<String, String> params);
+
+    /**
+     * Sets or updates data for a specific task.
+     *
+     * @param taskId  the ID of the task to use to update data - task does not have to contain the data that is updated
+     * @param dataSet the data to be set, organized as a map
+     * @param params  additional parameters for the operation
+     * @return the outcome of the set data operation
+     * @throws JsonProcessingException if there is an error processing JSON data
+     */
+    SetDataEventOutcome setData(String taskId, Map<String, Map<String, String>> dataSet, Map<String, String> params) throws JsonProcessingException;
+
+    /**
+     * Finds a specific case by its ID.
+     *
+     * @param caseId the ID of the case to find
+     * @return the found case
+     * @throws IllegalArgumentException if not found
+     */
+    Case findCase(String caseId);
+
+    /**
+     * Searches for cases matching the given predicate.
+     *
+     * @param processIdentifier reserved for interface compatibility; this implementation
+     *                          does not filter by process identifier and returns cases
+     *                          from all processes. Use the predicate parameter to filter
+     *                          by specific process(es) if needed.
+     * @param predicate         the criteria for filtering cases
+     * @param pageable          the pagination information
+     * @return a page of cases matching the criteria
+     */
+    Page<Case> searchCases(String processIdentifier, Predicate predicate, Pageable pageable);
+
+    /**
+     * Searches for cases using elastic search queries.
+     *
+     * @param elasticStringQueries a list of queries for filtering cases
+     * @param authPrincipalDto     the authorization principal used for the search
+     * @param pageable             the pagination information
+     * @param isIntersection       true to intersect results of all queries; false for union
+     * @return a page of cases matching the criteria
+     */
+    Page<Case> searchCases(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection);
+
+
+    /**
+     * Counts the number of cases that match the provided elastic search queries.
+     *
+     * @param elasticStringQueries a list of elastic search queries used for filtering cases
+     * @param authPrincipalDto     the authorization principal for the operation
+     * @param isIntersection       true to intersect the results of all queries; false for union
+     * @return the total number of cases matching the criteria
+     */
+    Long countCases(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Boolean isIntersection);
+
+    /**
+     * Creates a new case identified by the given process identifier.
+     *
+     * @param identifier       the process identifier for creating the case
+     * @param title            the title of the new case
+     * @param color            the color associated with the case
+     * @param authPrincipalDto the authorization principal for creating the case
+     * @param params           additional parameters for the operation
+     * @return the outcome of the case creation operation
+     */
+    CreateCaseEventOutcome createCaseByIdentifier(String identifier, String title, String color, AuthPrincipalDto authPrincipalDto, Map<String, String> params);
+
+    /**
+     * Deletes a specific case by its ID.
+     *
+     * @param caseId the ID of the case to delete
+     * @param params additional parameters for the operation
+     * @return the outcome of the delete operation
+     */
+    DeleteCaseEventOutcome deleteCase(String caseId, Map<String, String> params);
+
+    /**
+     * Finds a specific task by its ID.
+     *
+     * @param taskId the ID of the task to find
+     * @return the found task
+     * @throws IllegalArgumentException if not found
+     */
+    Task findTask(String taskId);
+
+    /**
+     * Searches for tasks matching the given predicate.
+     *
+     * @param processIdentifier reserved for interface compatibility; this implementation
+     *                          does not filter by process identifier and returns tasks
+     *                          from all processes. Use the predicate parameter to filter
+     *                          by specific process(es) if needed.
+     * @param predicate         the criteria for filtering tasks
+     * @param pageable          the pagination information
+     * @return a page of tasks matching the criteria
+     */
+    Page<Task> searchTasks(String processIdentifier, Predicate predicate, Pageable pageable);
+
+    /**
+     * Searches for tasks using elastic search queries.
+     *
+     * @param elasticStringQueries a list of queries for filtering tasks
+     * @param authPrincipalDto     the authorization principal used for the search
+     * @param pageable             the pagination information
+     * @param isIntersection       true to intersect results of all queries; false for union
+     * @return a page of tasks matching the criteria
+     */
+    Page<Task> searchTasks(List<String> elasticStringQueries, AuthPrincipalDto authPrincipalDto, Pageable pageable, Boolean isIntersection);
+
+    /**
+     * Assigns a specific task to a user.
+     *
+     * @param taskId           the ID of the task to assign
+     * @param authPrincipalDto the authorization principal used for the operation
+     * @param params           additional parameters for the operation
+     * @return the outcome of the task assignment operation
+     * @throws TransitionNotExecutableException if the task's transition cannot be executed
+     */
+    AssignTaskEventOutcome assignTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params) throws TransitionNotExecutableException;
+
+    /**
+     * Cancels a specific task.
+     *
+     * @param taskId           the ID of the task to cancel
+     * @param authPrincipalDto the authorization principal used for the operation
+     * @param params           additional parameters for the operation
+     * @return the outcome of the task cancellation operation
+     */
+    CancelTaskEventOutcome cancelTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params);
+
+    /**
+     * Marks a specific task as finished.
+     *
+     * @param taskId           the ID of the task to finish
+     * @param authPrincipalDto the authorization principal used for the operation
+     * @param params           additional parameters for the operation
+     * @return the outcome of the task completion operation
+     * @throws TransitionNotExecutableException if the task's transition cannot be executed
+     */
+    FinishTaskEventOutcome finishTask(String taskId, AuthPrincipalDto authPrincipalDto, Map<String, String> params) throws TransitionNotExecutableException;
+
+    /**
+     * Searches for users in a specific realm based on a predicate.
+     *
+     * @param predicate the criteria for filtering users
+     * @param pageable  the pagination information
+     * @param realmId   the ID of the realm to search
+     * @return a page of users matching the criteria
+     */
+    Page<User> searchUsers(Predicate predicate, Pageable pageable, String realmId);
+
+
+    /**
+     * Retrieves the system user of the application.
+     *
+     * @return the system user
+     */
+    AbstractUser getSystemUser();
+}
diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java
new file mode 100644
index 00000000000..79b9d673d89
--- /dev/null
+++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/actions/ActionApiMethods.java
@@ -0,0 +1,40 @@
+package com.netgrif.application.engine.adapter.spring.actions;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public enum ActionApiMethods {
+
+    GET_DATA("getData"),
+    SET_DATA("setData"),
+    FIND_CASE("findCase"),
+    COUNT_CASES("countCases"),
+    SEARCH_CASES("searchCases"),
+    CREATE_CASE_BY_IDENTIFIER("createCaseByIdentifier"),
+    DELETE_CASE("deleteCase"),
+    FIND_TASK("findTask"),
+    SEARCH_TASKS("searchTasks"),
+    ASSIGN_TASK("assignTask"),
+    CANCEL_TASK("cancelTask"),
+    FINISH_TASK("finishTask"),
+    SEARCH_USERS("searchUsers"),
+    GET_SYSTEM_USER("getSystemUser");
+
+    private String methodName;
+
+    private static final Map<String, ActionApiMethods> METHODS_BY_NAME_MAP = Arrays.stream(values()).collect(Collectors.toUnmodifiableMap(ActionApiMethods::getMethodName, e -> e));
+
+    ActionApiMethods(String methodName) {
+        this.methodName = methodName;
+    }
+
+    public String getMethodName() {
+        return methodName;
+    }
+
+
+    public static ActionApiMethods fromMethodName(String methodName) {
+        return METHODS_BY_NAME_MAP.get(methodName);
+    }
+}
diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/package-info.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/package-info.java
similarity index 94%
rename from nae-user-common/src/main/java/com/netgrif/application/engine/auth/package-info.java
rename to nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/package-info.java
index eb189e2a255..742273d6341 100644
--- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/package-info.java
+++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/package-info.java
@@ -10,7 +10,7 @@
         Group.class,
         User.class
 })
-package com.netgrif.application.engine.auth;
+package com.netgrif.application.engine.adapter.spring;
 
 import com.netgrif.application.engine.adapter.spring.petrinet.domain.PetriNet;
 import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.ProcessRole;
diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java
index dea75bc483b..eadac8c7e9b 100644
--- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java
+++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java
@@ -1,7 +1,10 @@
 package com.netgrif.application.engine.adapter.spring.utils;
 
+import com.netgrif.application.engine.adapter.spring.utils.exceptions.AmbiguousMethodCallException;
 import org.springframework.aop.framework.AopProxyUtils;
 
+import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -54,4 +57,85 @@ public static <I> int indexOfClass(List<I> list, Class<?> clazz) {
     }
 
 
+    /**
+     * Finds and returns the {@code Method} object for the specified method name and parameter types on the given bean.
+     * This method first attempts to locate the method on the target class of the bean.
+     * If not found, it tries to locate a method using superclasses of the parameter types.
+     *
+     * @param bean              the bean instance on which the method is to be located
+     * @param methodToExecute   the name of the method to be invoked
+     * @param requestParamTypes the types of the parameters expected by the method
+     * @return the {@code Method} object corresponding to the specified name and parameters
+     * @throws NoSuchMethodException        if the method cannot be found
+     * @throws AmbiguousMethodCallException if multiple methods match the specified criteria, resulting in ambiguity
+     */
+    public static Method findMethod(Object bean, String methodToExecute, Class<?>[] requestParamTypes)
+            throws NoSuchMethodException, AmbiguousMethodCallException {
+        Objects.requireNonNull(bean, "bean must not be null");
+        Objects.requireNonNull(methodToExecute, "methodToExecute must not be null");
+        final Class<?>[] paramTypes = (requestParamTypes == null) ? new Class<?>[0] : requestParamTypes;
+        try {
+            return NaeReflectionUtils.resolveClass(bean).getMethod(methodToExecute, paramTypes);
+        } catch (NoSuchMethodException e) {
+            return findMethodWithSuperClassParams(bean, methodToExecute, requestParamTypes, e);
+        }
+    }
+
+    /**
+     * Attempts to find a method on the target class of the given bean using superclass parameter types when an exact match is not found.
+     * If a method with the specified name is located, and the parameters can be assigned from the provided parameter types,
+     * the method is returned. If multiple methods match the criteria, an {@code AmbiguousMethodCallException} is thrown.
+     *
+     * @param bean              the bean instance on which the method is to be located
+     * @param methodToExecute   the name of the method to be invoked
+     * @param requestParamTypes the types of the parameters expected by the method
+     * @param caughtException   the exception caught from a previous attempt to find the method (used for re-throw if no method is found)
+     * @return the {@code Method} object that matches the specified criteria
+     * @throws NoSuchMethodException        if no suitable method is found
+     * @throws AmbiguousMethodCallException if multiple methods match the specified name and parameters, causing ambiguity
+     */
+    private static Method findMethodWithSuperClassParams(Object bean, String methodToExecute, Class<?>[] requestParamTypes,
+                                                  NoSuchMethodException caughtException)
+            throws NoSuchMethodException, AmbiguousMethodCallException {
+        Class<?> cls = NaeReflectionUtils.resolveClass(bean);
+        Method[] methods = Arrays.stream(cls.getMethods())
+                .filter(m -> !m.isBridge() && !m.isSynthetic())
+                .toArray(Method[]::new);
+        Method methodToInvoke = null;
+        outerLoop:
+        for (Method method : methods) {
+            if (!methodToExecute.equals(method.getName())) {
+                continue;
+            }
+
+            Class<?>[] paramTypes = method.getParameterTypes();
+            int requestParamsLen = requestParamTypes.length;
+            int paramsLen = paramTypes.length;
+
+            if (requestParamsLen == 0 && paramsLen == 0) {
+                methodToInvoke = method;
+                break;
+            } else if (paramsLen == 0 || paramsLen != requestParamsLen) {
+                continue;
+            }
+
+            for (int i = 0; i < requestParamTypes.length; ++i) {
+                if (!paramTypes[i].isAssignableFrom(requestParamTypes[i])) {
+                    continue outerLoop;
+                }
+            }
+
+            if (methodToInvoke != null) {
+                throw new AmbiguousMethodCallException(String.format("Method %s is ambiguous for the param types %s",
+                        methodToExecute, Arrays.toString(requestParamTypes)));
+            }
+            methodToInvoke = method;
+        }
+
+        if (methodToInvoke == null) {
+            throw caughtException;
+        } else {
+            return methodToInvoke;
+        }
+    }
 }
diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/exceptions/AmbiguousMethodCallException.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/exceptions/AmbiguousMethodCallException.java
new file mode 100644
index 00000000000..32ffaf83632
--- /dev/null
+++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/exceptions/AmbiguousMethodCallException.java
@@ -0,0 +1,32 @@
+package com.netgrif.application.engine.adapter.spring.utils.exceptions;
+
+import java.io.Serial;
+
+/**
+ * Exception thrown when a method call cannot be resolved unambiguously
+ * due to multiple possible matches.
+ */
+public final class AmbiguousMethodCallException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new exception with the specified detail message.
+     *
+     * @param message a description of the ambiguity that caused the exception
+     */
+    public AmbiguousMethodCallException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and cause.
+     *
+     * @param message   a description of the ambiguity that caused the exception
+     * @param throwable the cause of the exception
+     */
+    public AmbiguousMethodCallException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java
index 9820e6296b5..217c9e8c335 100644
--- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java
+++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java
@@ -12,6 +12,7 @@
 import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
 import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole;
 import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId;
+import com.querydsl.core.types.Predicate;
 import com.querydsl.core.types.dsl.BooleanExpression;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
@@ -588,6 +589,21 @@ public void updateAdminWithRoles(Collection<ProcessRole> roles) {
         log.debug("Admin [{}] now has [{}] process roles", admin.getUsername(), admin.getProcessRoles().size());
     }
 
+
+    /**
+     * Searches for users in the specified realm based on the provided predicate and pagination parameters.
+     *
+     * @param predicate the query conditions to filter users
+     * @param pageable  the pagination parameters for the search results
+     * @param realmId   the name of the realm, used to determine which collection to query
+     * @return a paginated list of users matching the predicate within the specified realm
+     */
+    @Override
+    public Page<User> search(Predicate predicate, Pageable pageable, String realmId) {
+        String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId);
+        return userRepository.findAllByQuery(predicate, pageable, mongoTemplate, collectionName);
+    } 
+
     protected User initializeNewUser(String username, String email, String firstName, String lastName, String password, String realmId) {
         log.trace("Initializing new user [{}] in realm [{}]", username, realmId);
         User user = new User();
diff --git a/nae-user-common/pom.xml b/nae-user-common/pom.xml
index c4e756a3014..6c38baf881f 100644
--- a/nae-user-common/pom.xml
+++ b/nae-user-common/pom.xml
@@ -117,17 +117,6 @@
             <version>${mapstruct.version}</version>
             <scope>provided</scope>
         </dependency>
-
-        <dependency>
-            <groupId>com.querydsl</groupId>
-            <artifactId>querydsl-core</artifactId>
-            <version>${querydsl.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>com.querydsl</groupId>
-            <artifactId>querydsl-apt</artifactId>
-            <version>${querydsl.version}</version>
-        </dependency>
         <dependency>
             <groupId>com.querydsl</groupId>
             <artifactId>querydsl-mongodb</artifactId>
@@ -164,30 +153,6 @@
                     </annotationProcessorPaths>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>com.mysema.maven</groupId>
-                <artifactId>apt-maven-plugin</artifactId>
-                <version>1.1.3</version>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>process</goal>
-                        </goals>
-                        <configuration>
-                            <outputDirectory>target/generated-sources/java</outputDirectory>
-                            <processors>
-                                <processor>
-                                    com.querydsl.apt.QuerydslAnnotationProcessor
-                                </processor>
-                            </processors>
-                            <options>
-                                <querydsl.excludedClasses>groovy.lang.MetaClass</querydsl.excludedClasses>
-                                <querydsl.unknownAsEmbeddable>true</querydsl.unknownAsEmbeddable>
-                            </options>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 
diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java
index 63e0111c821..9490c21308e 100644
--- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java
+++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java
@@ -5,6 +5,7 @@
 import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
 import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole;
 import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId;
+import com.querydsl.core.types.Predicate;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.mongodb.core.query.Query;
@@ -424,4 +425,14 @@ Page<AbstractUser> searchAllCoMembers(String query, Collection<ProcessResourceId
      * @return the updated user
      */
     AbstractUser changePassword(AbstractUser user, String newPassword, String oldPassword);
+
+    /**
+     * Searches for users in the specified realm based on the provided predicate and pagination parameters.
+     *
+     * @param predicate the query conditions to filter users
+     * @param pageable  the pagination parameters for the search results
+     * @param realmId   the name of the realm, used to determine which collection to query
+     * @return a paginated list of users matching the predicate within the specified realm
+     */
+    Page<User> search(Predicate predicate, Pageable pageable, String realmId);
 }
