Repository: holunda-io/camunda-bpm-taskpool
Pull request #783 (2023-04-11 16:56:37)

chore(deps): bump camunda-bpm.version from 7.18.0 to 7.19.0
Bumps `camunda-bpm.version` from 7.18.0 to 7.19.0.
Updates `camunda-bom` from 7.18.0 to 7.19.0
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/3b4fbf9731564e23fba74b542690c1a597546406"><code>3b4fbf9</code></a> chore(release): Prepare release: set version to 7.19.0</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/a50224487e0535685a2850727b81ba2c2908108b"><code>a502244</code></a> chore(license-book): update license book for 7.19.0</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/a3d44fefe77367dc171ed329f11109b8314f10e7"><code>a3d44fe</code></a> chore(webapps/locales): provide german translation for 7.19 (<a href="https://redirect.github.com/camunda/camunda-bpm-platform/issues/3292">#3292</a>)</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/9989cf3e769cfe45265b07c262aec1b0db1fcd24"><code>9989cf3</code></a> fix(webapp): fix third party bom creation (<a href="https://redirect.github.com/camunda/camunda-bpm-platform/issues/3280">#3280</a>)</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/25ff4513744de364a0f8a0749038d826aaba3b1a"><code>25ff451</code></a> chore(release): Prepare next development version: 7.19.0-SNAPSHOT</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/8293c8098e9e6c633409212a9a113e80484d6cb4"><code>8293c80</code></a> chore(release): Prepare release: set version to 7.19.0-alpha5</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/5eec464a9e0240389477313bb278dd7d1d822982"><code>5eec464</code></a> feat(example/invoice): add jakarta invoice example module (<a href="https://redirect.github.com/camunda/camunda-bpm-platform/issues/3270">#3270</a>)</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/693eb21e299304377a25e76dcf16f4df0e060dcb"><code>693eb21</code></a> chore(pom): add default maven repositories (<a href="https://redirect.github.com/camunda/camunda-bpm-platform/issues/3271">#3271</a>)</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/b0ec789c7530ba2407a23378a61d7640ffc82a9e"><code>b0ec789</code></a> chore(webapps): introduce logback config file</li>
<li><a href="https://github.com/camunda/camunda-bpm-platform/commit/12421ac32a655f5f6e46004b76aa9282c3251643"><code>12421ac</code></a> chore(webapps): enable jakarta unit tests (<a href="https://redirect.github.com/camunda/camunda-bpm-platform/issues/3264">#3264</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/camunda/camunda-bpm-platform/compare/7.18.0...7.19.0">compare view</a></li>
</ul>
</details>
<br />

Updates `camunda-bpm-spring-boot-starter-test` from 7.18.0 to 7.19.0


You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>> **Note**
> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.



Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 163dd227a..475b7c612 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,16 +35,16 @@ jobs:
         run: chmod +x ./mvnw
 
       - name: Build with Maven
-        run: ./mvnw clean verify -U -B -T4
+        run: ./mvnw clean verify -U -B -T4 -ntp
 
       - name: Prepare I-Tests
         run: mkdir -p target/jgiven-reports/json
 
       - name: Run I-Tests with Maven
-        run: ./mvnw integration-test failsafe:verify -Pitest -B -T4
+        run: ./mvnw integration-test failsafe:verify -Pitest -B -T4 -ntp
 
       - name: Deploy a new version to central
-        run: ./mvnw deploy -B -DskipTests -Prelease -Dgpg.keyname="${{secrets.GPG_KEYNAME}}" -Dgpg.passphrase="${{secrets.GPG_PASSPHRASE}}"
+        run: ./mvnw deploy -B -ntp -DskipTests -Prelease -Dgpg.keyname="${{secrets.GPG_KEYNAME}}" -Dgpg.passphrase="${{secrets.GPG_PASSPHRASE}}"
         env:
           OSS_CENTRAL_USERNAME: "${{ secrets.SONATYPE_USERNAME }}"
           OSS_CENTRAL_PASSWORD: "${{ secrets.SONATYPE_PASSWORD }}"
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index a3f9f1872..a407f99ee 100755
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,1 +1,1 @@
-distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.2/apache-maven-3.9.2-bin.zip
diff --git a/README.md b/README.md
index affe8e6a0..0139a71ca 100755
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 [![stable](https://img.shields.io/badge/lifecycle-STABLE-green.svg)](https://github.com/holisticon#open-source-lifecycle)
-[![Build Status](https://github.com/holunda-io/camunda-bpm-taskpool/workflows/Development%20braches/badge.svg)](https://github.com/holunda-io/camunda-bpm-taskpool/actions)
+[![Master and Snapshot release](https://github.com/holunda-io/camunda-bpm-taskpool/actions/workflows/release.yml/badge.svg)](https://github.com/holunda-io/camunda-bpm-taskpool/actions/workflows/release.yml)
 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.holunda.polyflow/polyflow-taskpool-dependencies/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.holunda.polyflow/polyflow-taskpool-dependencies/)
 [![Code Coverage](https://codecov.io/gh/holunda-io/camunda-bpm-taskpool/branch/master/graph/badge.svg)](https://codecov.io/gh/holunda-io/camunda-bpm-taskpool)
 [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6e15ca2f4ab64cfe85a736f324136086)](https://www.codacy.com/gh/holunda-io/camunda-bpm-taskpool/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=holunda-io/camunda-bpm-taskpool&amp;utm_campaign=Badge_Grade)
diff --git a/bom/datapool-dependencies/pom.xml b/bom/datapool-dependencies/pom.xml
index e94755223..a1c32bd6f 100644
--- a/bom/datapool-dependencies/pom.xml
+++ b/bom/datapool-dependencies/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../parent/pom.xml</relativePath>
   </parent>
 
diff --git a/bom/parent/pom.xml b/bom/parent/pom.xml
index 7cd71dd3d..2de680fd5 100644
--- a/bom/parent/pom.xml
+++ b/bom/parent/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-root</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
@@ -18,21 +18,21 @@
   <url>https://github.com/holunda-io/camunda-bpm-taskpool/</url>
 
   <properties>
-    <springboot.version>2.7.10</springboot.version>
+    <springboot.version>2.7.15</springboot.version>
     <camunda-commons-typed-values.version>7.18.0</camunda-commons-typed-values.version>
 
-    <axon-bom.version>4.6.3</axon-bom.version>
+    <axon-bom.version>4.6.6</axon-bom.version>
 
     <axon-kotlin.version>4.7.0</axon-kotlin.version>
     <axon-gateway-extension.version>1.1.2</axon-gateway-extension.version>
 
     <awaitability.version>4.2.0</awaitability.version>
-    <mockito-kotlin.version>4.1.0</mockito-kotlin.version>
-    <jgiven.version>1.2.5</jgiven.version>
+    <mockito-kotlin.version>5.0.0</mockito-kotlin.version>
+    <jgiven.version>1.3.0</jgiven.version>
+    <jgiven-kotlin.version>1.3.0.0</jgiven-kotlin.version>
 
     <pattern.class.itest>**/*ITest.*</pattern.class.itest>
     <pattern.package.itest>**/itest/**/*.*</pattern.package.itest>
-    <jgiven-kotlin.version>1.2.5.0</jgiven-kotlin.version>
   </properties>
 
   <modules>
@@ -296,13 +296,17 @@
 
   <build>
     <defaultGoal>clean package</defaultGoal>
+
+    <sourceDirectory>src/main/kotlin</sourceDirectory>
+    <testSourceDirectory>src/test/kotlin</testSourceDirectory>
+
     <pluginManagement>
       <plugins>
         <!-- Coverage metering -->
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.8.9</version>
+          <version>0.8.10</version>
           <executions>
             <execution>
               <id>pre-unit-test</id>
@@ -422,45 +426,11 @@
           </configuration>
         </plugin>
 
-        <plugin>
-          <!-- for packaging springboot application -->
-          <groupId>org.springframework.boot</groupId>
-          <artifactId>spring-boot-maven-plugin</artifactId>
-          <version>${springboot.version}</version>
-          <executions>
-            <execution>
-              <goals>
-                <goal>repackage</goal>
-              </goals>
-            </execution>
-          </executions>
-          <configuration>
-            <excludes>
-              <exclude>
-                <groupId>commons-logging</groupId>
-                <artifactId>commons-logging</artifactId>
-              </exclude>
-              <exclude>
-                <groupId>log4j</groupId>
-                <artifactId>log4j</artifactId>
-              </exclude>
-              <exclude>
-                <groupId>org.apache.logging.log4j</groupId>
-                <artifactId>log4j-core</artifactId>
-              </exclude>
-              <exclude>
-                <groupId>org.apache.logging.log4j</groupId>
-                <artifactId>log4j-api</artifactId>
-              </exclude>
-            </excludes>
-          </configuration>
-        </plugin>
-
         <plugin>
           <!-- for loading and unpacking artifacts -->
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-dependency-plugin</artifactId>
-          <version>3.5.0</version>
+          <version>3.6.0</version>
         </plugin>
 
         <plugin>
@@ -476,8 +446,8 @@
           <version>${kotlin.version}</version>
           <configuration>
             <jvmTarget>${java.version}</jvmTarget>
-            <apiVersion>1.5</apiVersion>
-            <languageVersion>1.5</languageVersion>
+            <apiVersion>1.8</apiVersion>
+            <languageVersion>1.8</languageVersion>
             <args>
               <arg>-Xjsr305=strict</arg>
             </args>
@@ -500,9 +470,6 @@
                 <goal>kapt</goal>
               </goals>
               <configuration>
-                <sourceDirs>
-                  <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
-                </sourceDirs>
                 <annotationProcessorPaths>
                   <annotationProcessorPath>
                     <groupId>org.springframework.boot</groupId>
@@ -517,12 +484,6 @@
               <goals>
                 <goal>compile</goal>
               </goals>
-              <configuration>
-                <sourceDirs>
-                  <sourceDir>${project.basedir}/target/generated-sources/swagger/src/main/java</sourceDir>
-                  <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
-                </sourceDirs>
-              </configuration>
             </execution>
             <execution>
               <id>test-compile</id>
@@ -552,7 +513,7 @@
 
         <plugin>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>3.0.0</version>
+          <version>3.1.2</version>
           <configuration>
             <useSystemClassLoader>false</useSystemClassLoader>
             <runOrder>random</runOrder>
@@ -563,7 +524,7 @@
             </excludes>
             <!-- Sets the VM argument line used when unit tests are run. -->
             <!-- prevent the annoying ForkedBooter process from stealing window focus on Mac OS -->
-            <argLine>-Djava.awt.headless=true ${surefireArgLine} -XX:+StartAttachListener</argLine>
+            <argLine>-Djava.awt.headless=true @{surefireArgLine} -XX:+StartAttachListener</argLine>
             <systemPropertyVariables>
               <jgiven.report.dir>${project.build.directory}/jgiven-reports</jgiven.report.dir>
             </systemPropertyVariables>
@@ -573,7 +534,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-failsafe-plugin</artifactId>
-          <version>3.0.0</version>
+          <version>3.1.2</version>
           <executions>
             <execution>
               <goals>
@@ -589,7 +550,7 @@
             </includes>
             <!-- Sets the VM argument line used when integration tests are run. -->
             <!-- prevent the annoying ForkedBooter process from stealing window focus on Mac OS -->
-            <argLine>-Djava.awt.headless=true ${failsafeArgLine} -XX:+StartAttachListener</argLine>
+            <argLine>-Djava.awt.headless=true @{failsafeArgLine} -XX:+StartAttachListener</argLine>
           </configuration>
         </plugin>
 
@@ -597,7 +558,7 @@
         <plugin>
           <groupId>org.jetbrains.dokka</groupId>
           <artifactId>dokka-maven-plugin</artifactId>
-          <version>1.8.10</version>
+          <version>1.9.0</version>
           <executions>
             <execution>
               <id>attach-javadocs</id>
@@ -613,7 +574,7 @@
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>build-helper-maven-plugin</artifactId>
-          <version>3.3.0</version>
+          <version>3.4.0</version>
           <executions>
             <execution>
               <phase>generate-sources</phase>
@@ -633,7 +594,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-source-plugin</artifactId>
-          <version>3.2.1</version>
+          <version>3.3.0</version>
           <executions>
             <execution>
               <id>attach-sources</id>
diff --git a/bom/taskpool-dependencies/pom.xml b/bom/taskpool-dependencies/pom.xml
index b67baef5f..2c16aabec 100644
--- a/bom/taskpool-dependencies/pom.xml
+++ b/bom/taskpool-dependencies/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../parent/pom.xml</relativePath>
   </parent>
 
diff --git a/core/bus-jackson/pom.xml b/core/bus-jackson/pom.xml
index 299382971..4da605ba7 100755
--- a/core/bus-jackson/pom.xml
+++ b/core/bus-jackson/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../bom/parent/pom.xml</relativePath>
   </parent>
 
diff --git a/core/datapool/datapool-api/pom.xml b/core/datapool/datapool-api/pom.xml
index 46142922d..46a6e1c55 100755
--- a/core/datapool/datapool-api/pom.xml
+++ b/core/datapool/datapool-api/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-datapool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-datapool-api</artifactId>
diff --git a/core/datapool/datapool-core/pom.xml b/core/datapool/datapool-core/pom.xml
index bcec1b9a0..ebef07c91 100644
--- a/core/datapool/datapool-core/pom.xml
+++ b/core/datapool/datapool-core/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-datapool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-datapool-core</artifactId>
diff --git a/core/datapool/datapool-event/pom.xml b/core/datapool/datapool-event/pom.xml
index 9314e97b2..cde70462f 100755
--- a/core/datapool/datapool-event/pom.xml
+++ b/core/datapool/datapool-event/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-datapool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-datapool-event</artifactId>
diff --git a/core/datapool/pom.xml b/core/datapool/pom.xml
index caf46fdc1..a5f0dca0e 100755
--- a/core/datapool/pom.xml
+++ b/core/datapool/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../bom/parent/pom.xml</relativePath>
   </parent>
 
diff --git a/core/taskpool/pom.xml b/core/taskpool/pom.xml
index 616b23b1a..43e02f17c 100755
--- a/core/taskpool/pom.xml
+++ b/core/taskpool/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../bom/parent/pom.xml</relativePath>
   </parent>
 
diff --git a/core/taskpool/taskpool-api/pom.xml b/core/taskpool/taskpool-api/pom.xml
index 63b42f860..3a675fd3c 100755
--- a/core/taskpool/taskpool-api/pom.xml
+++ b/core/taskpool/taskpool-api/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-taskpool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-taskpool-api</artifactId>
diff --git a/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/process/variable/ProcessVariableValue.kt b/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/process/variable/ProcessVariableValue.kt
index b26ac814f..cd43b2f2b 100644
--- a/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/process/variable/ProcessVariableValue.kt
+++ b/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/process/variable/ProcessVariableValue.kt
@@ -8,7 +8,7 @@ import org.camunda.bpm.engine.variable.value.TypedValue
  */
 interface ProcessVariableValue {
   val type: ProcessVariableValueType
-  val value: Any
+  val value: Any?
 }
 
 /**
@@ -23,7 +23,7 @@ data class TypedValueProcessVariableValue(override val value: TypedValue)
  * Implementation of the process variable value, where the value itself is represented by a 'primitive' class.
  */
 data class PrimitiveProcessVariableValue(
-  override val value: Any
+  override val value: Any?
 ) : ProcessVariableValue {
   override val type: ProcessVariableValueType = ProcessVariableValueType.PRIMITIVE
 }
@@ -46,7 +46,7 @@ enum class ProcessVariableValueType {
    */
   TYPE_VALUE,
   /**
-   * Some of the types are native to serialize. These are:
+   * Some types are native to serialize. These are:
    * - Numbers (Integer, Float, Double)
    * - Boolean
    * - String
diff --git a/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/task/EngineTaskCommandFilter.kt b/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/task/EngineTaskCommandFilter.kt
new file mode 100644
index 000000000..021952fd5
--- /dev/null
+++ b/core/taskpool/taskpool-api/src/main/kotlin/io/holunda/camunda/taskpool/api/task/EngineTaskCommandFilter.kt
@@ -0,0 +1,15 @@
+package io.holunda.camunda.taskpool.api.task
+
+import java.util.function.Predicate
+
+/**
+ * Filter for task commands.
+ */
+interface EngineTaskCommandFilter : Predicate<EngineTaskCommand> {
+  /**
+   * Tests if the task command should be sent.
+   * @param engineTaskCommand commend to test.
+   * @return true, if command should be emitted. Defaults to false.
+   */
+  override fun test(engineTaskCommand: EngineTaskCommand): Boolean = false
+}
diff --git a/core/taskpool/taskpool-core/pom.xml b/core/taskpool/taskpool-core/pom.xml
index 7036704af..5114fb412 100755
--- a/core/taskpool/taskpool-core/pom.xml
+++ b/core/taskpool/taskpool-core/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-taskpool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-taskpool-core</artifactId>
diff --git a/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregate.kt b/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregate.kt
index 560b20677..b7554eaa2 100755
--- a/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregate.kt
+++ b/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregate.kt
@@ -24,8 +24,8 @@ class TaskAggregate() {
   private lateinit var id: String
   internal lateinit var task: Task
 
-  private var deleted = false
-  private var completed = false
+  internal var deleted = false
+  internal var completed = false
 
   /**
    * This a non-static handler for create command.
@@ -277,5 +277,4 @@ class TaskAggregate() {
   fun on(event: TaskDeletedEngineEvent) {
     this.deleted = true
   }
-
 }
diff --git a/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregateEngineTaskCommandFilter.kt b/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregateEngineTaskCommandFilter.kt
new file mode 100644
index 000000000..4843aac40
--- /dev/null
+++ b/core/taskpool/taskpool-core/src/main/kotlin/io/holunda/polyflow/taskpool/core/task/TaskAggregateEngineTaskCommandFilter.kt
@@ -0,0 +1,43 @@
+package io.holunda.polyflow.taskpool.core.task
+
+import io.holunda.camunda.taskpool.api.task.CreateTaskCommand
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommand
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommandFilter
+import io.holunda.polyflow.taskpool.core.loadOptional
+import org.axonframework.eventsourcing.EventSourcingRepository
+import org.axonframework.messaging.GenericMessage
+import org.axonframework.messaging.unitofwork.DefaultUnitOfWork
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.stereotype.Component
+
+/**
+ * Filter checking the tasks in the event store.
+ */
+@Component
+@ConditionalOnProperty(
+  prefix = "polyflow.integration.collector.camunda.task.importer",
+  name = ["task-filter-type"],
+  havingValue = "eventstore",
+  matchIfMissing = false
+)
+class TaskAggregateEngineTaskCommandFilter(
+  private val eventSourcingRepository: EventSourcingRepository<TaskAggregate>
+) : EngineTaskCommandFilter {
+
+  override fun test(engineTaskCommand: EngineTaskCommand): Boolean {
+    return when (engineTaskCommand) {
+
+      is CreateTaskCommand -> {
+
+       DefaultUnitOfWork.startAndGet(GenericMessage.asMessage(engineTaskCommand)).executeWithResult {
+          eventSourcingRepository.loadOptional(engineTaskCommand.id)
+            .map { false } // if the task exists, the CreateCommand should not be emitted
+            .orElse(true) // if the task doesn't exist, emit the CreateTaskCommand
+        }.payload
+
+      }
+
+      else -> false // reject all others
+    }
+  }
+}
diff --git a/core/taskpool/taskpool-event/pom.xml b/core/taskpool/taskpool-event/pom.xml
index 698bdf8a6..2bb6e367e 100644
--- a/core/taskpool/taskpool-event/pom.xml
+++ b/core/taskpool/taskpool-event/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-taskpool-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-taskpool-event</artifactId>
diff --git a/docs/developer-guide/project-setup.md b/docs/developer-guide/project-setup.md
index 5f45badfa..42e36d70c 100644
--- a/docs/developer-guide/project-setup.md
+++ b/docs/developer-guide/project-setup.md
@@ -60,7 +60,7 @@ You can generate this by executing the following build command:
 ### Build Documentation
 
 We are using MkDocs for generation of a static site documentation and rely on Markdown as much as possible.
-MkDocs is a written in Python 3 and needs to be installed on your machine. For the installation please run the following
+MkDocs is written in Python 3 and needs to be installed on your machine. For the installation please run the following
 command from your command line:
 
 ```bash
diff --git a/docs/reference-guide/components/camunda-taskpool-collector.md b/docs/reference-guide/components/camunda-taskpool-collector.md
index f06408894..1923afb12 100644
--- a/docs/reference-guide/components/camunda-taskpool-collector.md
+++ b/docs/reference-guide/components/camunda-taskpool-collector.md
@@ -8,14 +8,14 @@ Taskpool Collector is a component deployed as a part of the process application
 (aside with Camunda BPM Engine) that is responsible for collecting information from
 the Camunda BPM Engine. It detects the _intent_ of the operations executed inside the engine
 and creates the corresponding commands for the Taskpool. The commands are enriched with data and transmitted to
-other taskpool components (via Command Bus).
+other Taskpool components (via Command Bus).
 
 In the following description, we use the terms _event_ and _command_. Event denotes an entity
 received from Camunda BPM Engine (from delegate event listener or from history event listener)
 which is passed over to the Taskpool Collector using internal **Spring eventing** mechanism. The Taskpool
 Collector converts the series of such events into a Taskpool Command - an entity carrying an intent
 of change inside the Taskpool core. Please note that _event_ has another meaning in CQRS/ES systems
-and other components of the Taskpool, but in the context of Taskpool collector an event always originates from
+and other components of the Taskpool, but in the context of Taskpool Collector an event always originates from
 Spring eventing.
 
 ### Features
@@ -25,6 +25,7 @@ Spring eventing.
 - Collection of process variable change events
 - Collection of task events and history events
 - Creation of task engine commands
+- Collection of tasks assignment information
 - Enrichment of task engine commands with process variables
 - Attachment of correlation information to task engine commands
 - Transmission of commands to Axon command bus
@@ -55,7 +56,7 @@ In order to enable collector component, include the Maven dependency to your pro
 
 ```
 
-Then activate the taskpool collector by providing the annotation on any Spring Configuration:
+Then activate the Taskpool Collector by providing the annotation on any Spring Configuration:
 
 ```java
 @Configuration
@@ -68,7 +69,7 @@ class MyProcessApplicationConfiguration {
 
 ### Event collection
 
-Taskpool Collector registers Spring Event Listener to the following events, fired by Camunda Eventing Engine Plugin:
+By default, Taskpool Collector registers Spring Event Listener to the following events, fired by Camunda Eventing Engine Plugin:
 
 * `DelegateTask` events:
   ** create
@@ -86,6 +87,22 @@ The events are transformed into corresponding commands and passed over to the pr
 is fired using custom listeners only and polyflow components don't rely on that but rather on own implementation of built-in (unskippable) listeners.
 For this purpose, it is important to disable Camunda Platform custom listeners by setting `camunda.bpm.eventing.task` property to `false`.   
 
+During collection of task information, you can control which listeners are registered. By default, all listeners are considered but you can change
+this behaviour by setting values of two properties:
+
+```yaml
+
+polyflow:
+  integration:
+    collector:
+      camunda:
+        task:
+          excludedTaskEventNames: assignment, delete
+          excludedHistoryEventNames: add-identity-link, delete-identity-link
+```
+This particular setting is helpful, if you want to disable assignment in the engine entirely and want to provide you own custom task assignment algorithm or
+use assignment based on process variables (see below for more details).
+
 ### Task commands enrichment
 
 Alongside with attributes received from the Camunda BPM engine, the engine task commands
@@ -126,7 +143,7 @@ A `VariableFilter` can be of the following type:
 Here is an example, how the process variable filter can configure the enrichment:
 
 ```java
-  @Configuration
+@Configuration
 public class MyTaskCollectorConfiguration {
 
   @Bean
@@ -168,6 +185,12 @@ public class MyTaskCollectorConfiguration {
       If you want to implement a custom enrichment, please provide your own implementation of the interface `VariablesEnricher`
       (register a Spring Component of the type) and set the property `polyflow.integration.collector.camunda.task.enricher.type` to `custom`.
 
+!!! warning
+      Avoid using a classic Camunda `TaskListener` which modifies process variables on task creation, since changes of those
+      listeners can't be used during task enrichment. A proper way to modify instance or task variables is to implement an ordered Spring
+      `EventListener` listening on `DelegateTask`, put it before the enricher by providing `@Order(TaskEventCollectorService.ORDER - 80)` and scope the event listener to 
+      the task of your interest using condition: `@EventListener(condition = "#delegateTask.taskDefinitionKey.equals('my-task-key') && #delegateTask.eventName.equals('create')")` 
+
 ### Data Correlation
 
 Apart from task payload attached by the enricher, the so-called _Correlation_ with data entries can
@@ -236,16 +259,67 @@ and the process reaches the task `task_approve_request`, the task will get the f
 
 > Please note that the logger root hierarchy is `io.holunda.polyflow.taskpool.collector`
 
-| Message Code     | Severity | Logger*               | Description                                                                                                                 | Meaning |
-|------------------|----------|:----------------------|:----------------------------------------------------------------------------------------------------------------------------|:--------| 
-| `COLLECTOR-001`  | `INFO`   |                       | Task commands will be collected.                                                                                            |         |
-| `COLLECTOR-002`  | `INFO`   |                       | Task commands not be collected.                                                                                             |         |
-| `COLLECTOR-005`  | `TRACE`  | `.process.definition` | Sending process definition command: $command                                                                                |         |
-| `COLLECTOR-006`  | `TRACE`  | `.process.instance`   | Sending process instance command: $command                                                                                  |         |
-| `COLLECTOR-007`  | `TRACE`  | `.process.variable`   | Sending process variable command: $command                                                                                  |         |
-| `COLLECTOR-008`  | `TRACE`  | `.task`               | Sending engine task command: $command.                                                                                      |         |
-| `ENRICHER-001`   | `INFO`   |                       | Task commands will be enriched with process variables.                                                                      |         |
-| `ENRICHER-002`   | `INFO`   |                       | Task commands will not be enriched.                                                                                         |         |
-| `ENRICHER-003`   | `INFO`   |                       | Task commands will be enriched by a custom enricher.                                                                        |         |
-| `ENRICHER-004`   | `DEBUG`  | `.task.enricher`      | Could not enrich variables from running execution ${command.sourceReference.executionId}, since it doesn't exist (anymore). |         |
+| Message Code     | Severity | Logger*               | Description                                                                                                                 | Meaning  |
+|------------------|----------|:----------------------|:----------------------------------------------------------------------------------------------------------------------------|:---------| 
+| `COLLECTOR-001`  | `INFO`   |                       | Task commands will be collected.                                                                                            |          |
+| `COLLECTOR-002`  | `INFO`   |                       | Task commands not be collected.                                                                                             |          |
+| `COLLECTOR-005`  | `TRACE`  | `.process.definition` | Sending process definition command: $command                                                                                |          |
+| `COLLECTOR-006`  | `TRACE`  | `.process.instance`   | Sending process instance command: $command                                                                                  |          |
+| `COLLECTOR-007`  | `TRACE`  | `.process.variable`   | Sending process variable command: $command                                                                                  |          |
+| `COLLECTOR-008`  | `TRACE`  | `.task`               | Sending engine task command: $command.                                                                                      |          |
+| `ENRICHER-001`   | `INFO`   |                       | Task commands will be enriched with process variables.                                                                      |          |
+| `ENRICHER-002`   | `INFO`   |                       | Task commands will not be enriched.                                                                                         |          |
+| `ENRICHER-003`   | `INFO`   |                       | Task commands will be enriched by a custom enricher.                                                                        |          |
+| `ENRICHER-004`   | `DEBUG`  | `.task.enricher`      | Could not enrich variables from running execution ${command.sourceReference.executionId}, since it doesn't exist (anymore). |          |
+
+### Task Assignment
+
+User task assignment is a core functionality for every process application fostering task-oriented work. By default, Taskpool Collector uses
+information from Camunda User Task and maps that one-to-one to properties of the user task commands. The task attribute
+`assignee`, `candidateUsers` and `candidateGroups` are mapped to the corresponding attributes automatically.
+
+To control the task assignment mode you can configure Taskpool Collector using application properties. The property 
+`polyflow.integration.collector.camunda.task.assigner.type` has the following values:
+
+* `no`: No additional assignment takes place, the Camunda task attributes are used (default)
+* `process-variables`: Use process variables for assignment information, see below
+* `custom`: User provides own implementation implementing a bean implementing `TaskAssigner` interface.
+
+If the value is set to `process-variables`, you can set up a constant mapping defining the process variables carrying the assignment
+information. The corresponding properties are:
+
+```yaml
+polyflow:
+  integration:
+    collector:
+      camunda:
+        task:
+          assigner:
+            type: process-variables
+            assignee: my-assignee-var
+            candidateUsers: my-candidate-users-var 
+            candidateGroup: my-candidate-group-var
+```
+
+### Task Importer
+
+Alongside with the event-based Task Collector based on Camunda Eventing, there exists a dedicated service which can query Camunda database for existing
+user tasks and publish the results. In order to avoid duplications in tasks, the collected tasks are filtered by a special filter. Currently, you may choose
+between the supplied `eventstore` filter or supply your own `custom` filter by providing your own implementation of a `EngineTaskCommandFilter` interface as 
+a Spring Bean. If you want to use this task importer facility, you need to activate it first in your application configuration.
+
+The following property block is used for configuration:
+
+```yaml
+polyflow:
+  integration:
+    collector:
+      camunda:
+        task:
+          importer:
+            enabled: true
+            task-filter-type: eventstore
+```
 
+By doing so, the `TaskServiceCollectorService` Bean is made available and can be used to trigger the import. The `eventstore` filter is useful in scenarios,
+in which the [Taskpool Core](./core-taskpool) is deployed on together with Taskpool Collector as part of the Process Application.
diff --git a/docs/reference-guide/components/view-api-client.md b/docs/reference-guide/components/view-api-client.md
index 27d0569aa..af5b9c400 100644
--- a/docs/reference-guide/components/view-api-client.md
+++ b/docs/reference-guide/components/view-api-client.md
@@ -5,7 +5,7 @@ in order to query the configured views. By doing so, it defines an easy-to-use A
 
 ## Usage
 
-Pleas put the following component to you class path:
+Please put the following component to you class path:
 
 ```xml
 <dependency>
@@ -22,4 +22,12 @@ The components available are:
 * `io.holunda.polyflow.view.ProcessVariableQueryClient`
 * `io.holunda.polyflow.view.TaskQueryClient`
 
+To initialize the client, you need to pass the `queryGateway` to it:
+
+```kotlin
+@Bean
+fun myTaskClient(queryGateway: QueryGateway) = TaskQueryClient(queryGateway)
+
+```
+
 If you are using Kotlin, you might like the extension functions of the `QueryGateway` provided by `io.holunda.polyflow.view.QueryGatewayExt` object.  
diff --git a/docs/reference-guide/components/view-api.md b/docs/reference-guide/components/view-api.md
index b5eb4f0ec..af9e7d127 100644
--- a/docs/reference-guide/components/view-api.md
+++ b/docs/reference-guide/components/view-api.md
@@ -21,18 +21,19 @@ and generic query paging and sorting.
 
 The Task API allows to query for tasks handled by the task-pool.
 
-| Query Type                        | Description                                                                                                                                    | Payload types                   | In-Memory | JPA        | Mongo DB |
-|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|-----------|------------|----------|
-| AllTasksQuery                     | Retrieves a list of tasks applying additional filters                                                                                          | List<Task>                      | yes       | yes        | no       |
-| TasksForUserQuery                 | Retrieves a list of tasks accessible by the user and applying additional filters                                                               | List<Task>                      | yes       | yes        | yes      |
-| TasksForGroupQuery                | Retrieves a list of tasks accessible by the user's group and applying additional filters                                                       | List<Task>                      | yes       | yes        | no       |
-| TaskForIdQuery                    | Retrieves a task by id (without any other filters)                                                                                             | Task or null                    | yes       | yes        | yes      |
-| TasksForApplicationQuery          | Retrieves all tasks by given application name (without any further filters)                                                                    | List<Task>                      | yes       | yes        | yes      |
-| AllTasksWithDataEntriesQuery      | Retrieves a list of tasks applying additional filters and correlates result with data entries, if available                                    | List<(Task, List<DataEntry>)    | yes       | incubation | no       |
-| TasksWithDataEntriesForGroupQuery | Retrieves a list of tasks accessible by the user's group and applying additional filters and correlates result with data entries, if available | List<(Task, List<DataEntry>)    | yes       | incubation | no       |
-| TasksWithDataEntriesForUserQuery  | Retrieves a list of tasks accessible by the user and applying additional filters and correlates result with data entries, if available         | List<(Task, List<DataEntry>)    | yes       | incubation | yes      |
-| TaskWithDataEntriesForIdQuery     | Retrieves a task by id and correlates result with data entries, if available                                                                   | (Task, List<DataEntry>) or null | yes       | yes        | yes      |
-| TaskCountByApplicationQuery       | Counts tasks grouped by application names, useful for monitoring                                                                               | List<(ApplicationName, Count)>  | yes       | no         | yes      |
+| Query Type                          | Description                                                                                                                                    | Payload types                   | In-Memory | JPA        | Mongo DB |
+|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|-----------|------------|----------|
+| AllTasksQuery                       | Retrieves a list of tasks applying additional filters                                                                                          | List<Task>                      | yes       | yes        | no       |
+| TasksForUserQuery                   | Retrieves a list of tasks accessible by the user and applying additional filters                                                               | List<Task>                      | yes       | yes        | yes      |
+| TasksForGroupQuery                  | Retrieves a list of tasks accessible by the user's group and applying additional filters                                                       | List<Task>                      | yes       | yes        | no       |
+| TasksForCandidateUserAndGroupQuery  | Retrieves a list of tasks accessible by the user because listed as candidate and the user's group and applying additional filters              | List<Task>                      | yes       | yes        | no       |
+| TaskForIdQuery                      | Retrieves a task by id (without any other filters)                                                                                             | Task or null                    | yes       | yes        | yes      |
+| TasksForApplicationQuery            | Retrieves all tasks by given application name (without any further filters)                                                                    | List<Task>                      | yes       | yes        | yes      |
+| AllTasksWithDataEntriesQuery        | Retrieves a list of tasks applying additional filters and correlates result with data entries, if available                                    | List<(Task, List<DataEntry>)    | yes       | incubation | no       |
+| TasksWithDataEntriesForGroupQuery   | Retrieves a list of tasks accessible by the user's group and applying additional filters and correlates result with data entries, if available | List<(Task, List<DataEntry>)    | yes       | incubation | no       |
+| TasksWithDataEntriesForUserQuery    | Retrieves a list of tasks accessible by the user and applying additional filters and correlates result with data entries, if available         | List<(Task, List<DataEntry>)    | yes       | incubation | yes      |
+| TaskWithDataEntriesForIdQuery       | Retrieves a task by id and correlates result with data entries, if available                                                                   | (Task, List<DataEntry>) or null | yes       | yes        | yes      |
+| TaskCountByApplicationQuery         | Counts tasks grouped by application names, useful for monitoring                                                                               | List<(ApplicationName, Count)>  | yes       | no         | yes      |
  
 
 ### Process Definition API
@@ -90,7 +91,7 @@ for more details. Please note, that not all implementations are implementing thi
 
 ## Filtering, Paging and Sorting
 
-Task API and Data Entries API supports filtering, paging and sorting in queries resulting in multiple results. For Task API these are `AllTasksQuery`, `TasksForGroupQuery`, `TasksForUserQuery`,
+Task API and Data Entries API supports filtering, paging and sorting in queries resulting in multiple results. For Task API these are `AllTasksQuery`, `TasksForGroupQuery`, `TasksForUserQuery`, `TasksForCandidateUserAndGroupQuery`,
 `AllTasksWithDataEntriesQuery`, `TasksWithDataEntriesForGroupQuery`, `TasksWithDataEntriesForUserQuery` and for Data Entries API these are `DataEntriesForUserQuery` and `DataEntriesQuery`. 
 The queries implement the `PageableSortableQuery` interface, allowing to limit the amount of results and provide an optional sorting:
 
@@ -104,29 +105,41 @@ interface PageableSortableQuery {
 The `page` parameter denotes the page number to deliver (starting with `0`). The `size` parameter denotes the number of elements on a page. By default, the `page` is set to `0`
 and the size is set to `Int.MAX`. 
 
-An optional `sort` parameter allows to sort the results by a field attribute.  The format of the `sort` string is `<+|->filedName`, `+fieldName` means sort by `fieldName` ascending,
-`-fieldName` means sort by `fieldName` descending. The field must be a direct member of the result (`Task`, `TaskWithDataEntries` or `DataEntry`) and must be one of the following type:
+An optional `sort` parameter allows to sort the results by a field attribute. The format of the `sort` string is `<+|->fieldName`, `+fieldName` means sort by `fieldName` ascending,
+`-fieldName` means sort by `fieldName` descending. The field must be a direct member of the result (`Task` for queries on `Task` and `TaskWithDataEntries` or `DataEntry`) and must be one of the following type:
 
 * java.lang.Integer
 * java.lang.String
 * java.util.Date
 * java.time.Instant
 
-To filter the results, you might supply a list of filters. A filter is an expression in format `fieldName<op>value`, where `fieldName` is addressing the attribute of the search result,
+To filter the results, you can supply a list of filters. A filter is an expression in format `fieldName<op>value`, where `fieldName` is addressing the attribute of the search result,
 `<op>` is one of `<`, `=`, `%` and `>` and `value` is a string representation of the values. The `fieldName` can point to an attribute of the result entity itself (`Task`, `TaskWithDataEntries` 
-or `DataEntry`) or point to the attribute inside the payload. To avoid a possible name clash, you must prefix the field name with `task.` if you want to filter on direct attributes of a task,
-and you must prefix the field name with `data` if you want to filter on direct attributes of a dta entry. For example, `task.priority=50` would deliver tasks with priority set to 50,
+or `DataEntry`) or point to the attribute inside the payload. To avoid name clashes, you must prefix the field name with `task` if you want to filter on direct attributes of a task,
+and you must prefix the field name with `data` if you want to filter on direct attributes of a data entry. For example, `task.priority=50` would deliver tasks with priority set to 50,
 and `data.entryType=info.polyflow.Order` will deliver data entries of type `info.polyflow.Order` only.
 
 Following operations are supported:
 
-| Filter | Operation    | In-Memory    | JPA (Task Attributes) | JPA (Data Entries Attributes)                                      | Mongo DB (Task Attributes) | Mongo DB (Data Entries Attributes) |  
-|--------|--------------|--------------|-----------------------|--------------------------------------------------------------------|----------------------------|------------------------------------|
-| `<`    | Less than    | all, payload | none                  | none                                                               | all, payload               | all, payload                       | 
-| `>`    | Greater than | all, payload | none                  | none                                                               | all, payload               | all, payload                       |
-| `=`    | Equals       | all, payload | business key, payload | entry id, entry type, type, payload, processing state, user status | all, payload               | all, payload                       |
-| `%`    | Like         | all, payload | name, description     | none                                                               | none                       | none                               |
+| Filter | Operation    | In-Memory    | JPA (Task Attributes)                                             | JPA (Data Entries Attributes)                                            | Mongo DB (Task Attributes) | Mongo DB (Data Entries Attributes) |  
+|--------|--------------|--------------|-------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------|------------------------------------|
+| `<`    | Less than    | all, payload | `followUpDate`, `dueDate`                                         | none                                                                     | all, payload               | all, payload                       | 
+| `>`    | Greater than | all, payload | `followUpDate`, `dueDate`                                         | none                                                                     | all, payload               | all, payload                       |
+| `=`    | Equals       | all, payload | payload, `businessKey`, `followUpDate`, `dueDate`, `priority`     | `entryId`, `entryType`, `type`, payload, `processingState`, `userStatus` | all, payload               | all, payload                       |
+| `%`    | Like         | all, payload | `businessKey`, `name`, `description`, `processName`, `textSearch` | none                                                                     | none                       | none                               |
 
+!!! info
+    There are several special reserved filters which can be passed to the task query: `task.processName=<value>` checks equality of the process name, `task.processName%<value>` makes a like-search on
+    process name, `task.textSearch%some-substring` makes a special OR-combined like-search on task name, task description and task process name. 
 
-If the field name has no prefix of above, it is considered as an attribute inside the payload of data entry or enriched variables of a user task. For example, imagine
-you have a data entry with payload attributes `{ "attribute": "value", "another": 45 }`. In order to search for those, just specify `attribute=value` in your filter criteria. 
+If the field name does not have one of the above prefixes, it is considered as an attribute inside the payload of data entry or enriched variables of a user task. For example, imagine
+you have a data entry with payload attributes `{ "attribute": "value", "another": 45 }`. In order to search for those, just specify `attribute=value` in your filter criteria.
+
+Filters are composed with logical AND, meaning that all given filters have to match in order for a task to be included in the result of the query. For example, given the filters
+`task.priority=50` and `foo=bar`, the query result would only contain tasks that have a priority of 50 **and** a payload attribute named foo with the value bar.
+
+!!! warning
+    The [JPA View](view-jpa.md) has a different implementation when applying filters. Filters that target the same attribute are OR-composed before being AND-composed
+    with filters that target other attributes. For example, given the filters `customerName=ABC`, `customerName=DEF` and `task.priority=50`, the filters for the
+    customer name would first get OR-composed before being AND-composed with the task priority filter, resulting in a filtering expression logically equivalent to
+    `(customerName=ABC OR customerName=DEF) AND task.priority=50`.
diff --git a/docs/reference-guide/components/view-jpa.md b/docs/reference-guide/components/view-jpa.md
index 5b39fdce1..1df9bf250 100644
--- a/docs/reference-guide/components/view-jpa.md
+++ b/docs/reference-guide/components/view-jpa.md
@@ -5,7 +5,7 @@ pageId: view-jpa
 ### Purpose
 
 The JPA View is component responsible for creating read-projections of tasks and business data entries. It currently implements
-Datapool View API and Taskpool API and persists the projection as entities and relations in a RDBMS using JPA. It is a useful
+Datapool View API and Taskpool API and persists the projection as entities and relations in a RDBMS using JPA. It is useful
 if the JPA persistence is already used in the project setup.
 
 ### Features
@@ -59,18 +59,29 @@ configuration of this indexing process by the following configuration options:
 
 ```yml
 polyflow.view.jpa:
-  payload-attribute-level-limit: 2
   stored-items: task, data-entry, process-instance, process-definition
+  payload-attribute-level-limit: 2
   data-entry-filters:
     include: myProperty2.myOtherEmbeddedProperty3, myProperty2.myOtherEmbeddedProperty2
 #    exclude: myProperty
+  task-filters:
+    exclude: processVariableWithVeryLongText
+
 ```
 
-In the example below you see the configuration of the limit of keying depth and usage of include/exclude filters of the keys.
+In the example above you see the configuration of the limit of keying depth and usage of include/exclude filters of the keys.
 In addition, the `stored-items` property is holding a set of items to be persisted to the database. The possible values of 
 stored items are: `task`, `data-entry`, `process-instance` and `process-definition`. By setting this property, you can disable
 storage of items not required by your application and save space consumption of your database. The property defaults to `data-entry`.
 
+The attributes `data-entry-filters` and `task-filters` hold `include` / `exclude` lists of property paths which will be taken in 
+consideration during the search index creation.
+
+!!! note
+    Please make sure you understand that the **payload enrichment** performed during collection and **indexing for search** are two different
+    operations. It is perfectly fine to have a large JSON payload attached to the task, but it makes no sense to make the entire payload searchable,
+    at lease using JPA View.
+
 ### Entity Scan
 
 The JPA View utilizes Spring Data repositories and Hibernate entities inside the persistence layer. As a result, it declares a `@EntityScan` 
diff --git a/integration/camunda-bpm/engine-client/pom.xml b/integration/camunda-bpm/engine-client/pom.xml
index b9e2d3fcb..136cf2848 100644
--- a/integration/camunda-bpm/engine-client/pom.xml
+++ b/integration/camunda-bpm/engine-client/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-camunda-bpm-engine-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-camunda-bpm-engine-client</artifactId>
@@ -22,7 +22,7 @@
       <exclusions>
         <exclusion>
           <groupId>org.axonframework</groupId>
-          <artifactId>axon-server-conector</artifactId>
+          <artifactId>axon-server-connector</artifactId>
         </exclusion>
       </exclusions>
     </dependency>
diff --git a/integration/camunda-bpm/pom.xml b/integration/camunda-bpm/pom.xml
index 912300bf2..29b0242f3 100644
--- a/integration/camunda-bpm/pom.xml
+++ b/integration/camunda-bpm/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../bom/parent/pom.xml</relativePath>
   </parent>
 
@@ -14,9 +14,9 @@
   <packaging>pom</packaging>
 
   <properties>
-    <camunda-bpm.version>7.18.0</camunda-bpm.version>
+    <camunda-bpm.version>7.19.0</camunda-bpm.version>
     <camunda-bpm-assert.version>${camunda-bpm.version}</camunda-bpm-assert.version>
-    <camunda-platform-7-mockito.version>6.18.0</camunda-platform-7-mockito.version>
+    <camunda-platform-7-mockito.version>6.19.0</camunda-platform-7-mockito.version>
   </properties>
 
   <modules>
diff --git a/integration/camunda-bpm/springboot-autoconfigure/pom.xml b/integration/camunda-bpm/springboot-autoconfigure/pom.xml
index fc1e82a76..91c0f4a33 100755
--- a/integration/camunda-bpm/springboot-autoconfigure/pom.xml
+++ b/integration/camunda-bpm/springboot-autoconfigure/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-camunda-bpm-engine-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-camunda-bpm-springboot-autoconfigure</artifactId>
diff --git a/integration/camunda-bpm/springboot-starter/pom.xml b/integration/camunda-bpm/springboot-starter/pom.xml
index b8b9836e2..1aab072eb 100755
--- a/integration/camunda-bpm/springboot-starter/pom.xml
+++ b/integration/camunda-bpm/springboot-starter/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-camunda-bpm-engine-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-camunda-bpm-springboot-starter</artifactId>
diff --git a/integration/camunda-bpm/taskpool-collector/pom.xml b/integration/camunda-bpm/taskpool-collector/pom.xml
index e1adacc01..781c4afa6 100755
--- a/integration/camunda-bpm/taskpool-collector/pom.xml
+++ b/integration/camunda-bpm/taskpool-collector/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-camunda-bpm-engine-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-camunda-bpm-taskpool-collector</artifactId>
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/TaskEntityExtensions.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/TaskEntityExtensions.kt
index 3aaeedc6a..b271bb3c0 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/TaskEntityExtensions.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/TaskEntityExtensions.kt
@@ -1,8 +1,11 @@
 package io.holunda.polyflow.taskpool
 
+import io.holunda.camunda.taskpool.api.task.CreateTaskCommand
 import org.apache.commons.lang3.reflect.FieldUtils
+import org.camunda.bpm.engine.delegate.TaskListener
 import org.camunda.bpm.engine.impl.persistence.entity.PropertyChange
 import org.camunda.bpm.engine.impl.persistence.entity.TaskEntity
+import org.camunda.bpm.engine.task.IdentityLinkType
 
 /**
  * Reads identity changes from the task entity.
@@ -22,4 +25,28 @@ fun TaskEntity.isAssigneeChange(): Boolean =
 /**
  * Checks if the task entity has changed properties.
  */
-fun TaskEntity.hasChangedProperties(): Boolean = this.propertyChanges.isNotEmpty()
\ No newline at end of file
+fun TaskEntity.hasChangedProperties(): Boolean = this.propertyChanges.isNotEmpty()
+
+/**
+ * Creates a new CreateTaskCommand from this task entity. The resulting command has no correlations and is not enriched.
+ * @param applicationName application name.
+ * @return create task command.
+ */
+fun TaskEntity.asCreateCommand(applicationName: String) = CreateTaskCommand(
+    id = this.id,
+    assignee = this.assignee,
+    candidateGroups = this.candidates.filter { it.groupId != null }.map { it.groupId }.toSet(),
+    candidateUsers = this.candidates.filter { it.userId != null && it.type == IdentityLinkType.CANDIDATE }.map { it.userId }.toSet(),
+    createTime = this.createTime,
+    description = this.description,
+    dueDate = this.dueDate,
+    followUpDate = this.followUpDate,
+    eventName = TaskListener.EVENTNAME_CREATE,
+    name = this.name,
+    owner = this.owner,
+    priority = this.priority,
+    formKey = this.formKey(),
+    taskDefinitionKey = this.taskDefinitionKey,
+    businessKey = this.execution.businessKey,
+    sourceReference = this.sourceReference(applicationName)
+  )
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/CamundaTaskpoolCollectorProperties.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/CamundaTaskpoolCollectorProperties.kt
index 078a06b74..c0a696ce7 100755
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/CamundaTaskpoolCollectorProperties.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/CamundaTaskpoolCollectorProperties.kt
@@ -1,5 +1,7 @@
 package io.holunda.polyflow.taskpool.collector
 
+import io.holunda.polyflow.taskpool.collector.task.assigner.ProcessVariableTaskAssignerMapping
+import org.camunda.bpm.engine.delegate.TaskListener
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.context.properties.ConfigurationProperties
 import org.springframework.boot.context.properties.ConstructorBinding
@@ -52,11 +54,49 @@ data class CamundaTaskCollectorProperties(
    */
   @NestedConfigurationProperty
   val enricher: TaskCollectorEnricherProperties = TaskCollectorEnricherProperties(),
+
+  /**
+   * Task assigner properties.
+   */
+  @NestedConfigurationProperty
+  val assigner: TaskAssignerProperties = TaskAssignerProperties(),
+
   /**
    * Flag to enable or disable the collector.
    */
-  val enabled: Boolean = true
-)
+  val enabled: Boolean = true,
+
+  /**
+   * Properties of task importer.
+   */
+  @NestedConfigurationProperty
+  val importer: TaskImporterProperties = TaskImporterProperties(),
+
+  /**
+   * List of task events to be excluded from collector. Defaults to empty list, so all events are collected.
+   * Possible values are constants defined in [TaskListener].
+   */
+  val excludedTaskEventNames: List<String> = listOf(),
+
+  /**
+   * List of history events to restrict (HistoricTaskInstanceEventEntity, HistoricIdentityLinkLogEventEntity). Defaults to empty list, so all events are collected.
+   * Possible values are constants defined in [HistoryEventTypes] + "update".
+   */
+  val excludedHistoryEventNames: List<String> = listOf()
+) {
+  /**
+   * Determines if the provided event name should be collected.
+   * @param eventName event name to check.
+   * @return true if not excluded.
+   */
+  fun collectTaskEvent(eventName: String): Boolean = !excludedTaskEventNames.contains(eventName)
+  /**
+   * Determines if the provided event name should be collected.
+   * @param eventName event name to check.
+   * @return true if not excluded.
+   */
+  fun collectHistoryEvent(eventName: String): Boolean = !excludedHistoryEventNames.contains(eventName)
+}
 
 /**
  * Process variable properties.
@@ -100,6 +140,26 @@ enum class TaskCollectorEnricherType {
   custom
 }
 
+/**
+ * Type of task assigner.
+ */
+enum class TaskAssignerType {
+  /**
+   * Empty assigner, use information from Camunda task.
+   */
+  no,
+
+  /**
+   * Use process variables for assignment information.
+   */
+  processVariables,
+
+  /**
+   * Custom assigner.
+   */
+  custom
+}
+
 /**
  * Properties controlling the transfer of process definitions deployments.
  */
@@ -124,3 +184,61 @@ data class CamundaProcessInstanceCollectorProperties(
    */
   val enabled: Boolean = true
 )
+
+/**
+ * Properties to set up the task assigner.
+ */
+@ConstructorBinding
+data class TaskAssignerProperties(
+  /**
+   * Configures assigner type.
+   */
+  val type: TaskAssignerType = TaskAssignerType.no,
+  /**
+   * Process variable carrying the assignee information used by the process variable task assigner.
+   */
+  val assignee: String? = null,
+  /**
+   * Process variable carrying the candidateUsers information used by the process variable task assigner.
+   */
+  val candidateUsers: String? = null,
+  /**
+   * Process variable carrying the candidateGroups information used by the process variable task assigner.
+   */
+  val candidateGroups: String? = null
+) {
+  /**
+   * Constructs mapping from properties.
+   */
+  fun toMapping(): ProcessVariableTaskAssignerMapping = ProcessVariableTaskAssignerMapping(
+    assignee = assignee,
+    candidateUsers = candidateUsers,
+    candidateGroups = candidateGroups,
+  )
+}
+
+/**
+ * Configuration of the task importer.
+ */
+@ConstructorBinding
+data class TaskImporterProperties(
+  /**
+   * Enables or disabled importer. Defaults to false.
+   */
+  val enabled: Boolean = false,
+
+  /**
+   * Configures the type of engine task command filter.
+   * Defaults to `eventstore` allowing co-located deployed Taskpool Core to be used as a reference to filter commands.
+   */
+  val taskFilterType: EngineTaskCommandFilterType = EngineTaskCommandFilterType.eventstore
+)
+
+/**
+ * Type
+ */
+enum class EngineTaskCommandFilterType {
+  eventstore,
+  custom
+}
+
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/process/definition/ProcessDefinitionService.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/process/definition/ProcessDefinitionService.kt
index aa0c7c404..61846b701 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/process/definition/ProcessDefinitionService.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/process/definition/ProcessDefinitionService.kt
@@ -50,7 +50,15 @@ class ProcessDefinitionService(
       this.processDefinitions.clear()
     }
     this.processDefinitions.addAll(newDefinitions)
-    return newDefinitions.map { it.asCommand(applicationName = collectorProperties.applicationName, formKey = formService.getStartFormKey(it.id)) }
+    return newDefinitions.map { it.asCommand(applicationName = collectorProperties.applicationName, formKey = getStartFormKey(it, formService)) }
+  }
+
+  private fun getStartFormKey(processDefinitionEntity: ProcessDefinitionEntity, formService: FormService): String? {
+    return if (processDefinitionEntity.hasStartFormKey) {
+      formService.getStartFormKey(processDefinitionEntity.id)
+    } else {
+      null
+    }
   }
 
   /**
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskAssigner.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskAssigner.kt
new file mode 100644
index 000000000..76f4adfa6
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskAssigner.kt
@@ -0,0 +1,15 @@
+package io.holunda.polyflow.taskpool.collector.task
+
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommand
+
+/**
+ * Hook for assignment changes.
+ */
+interface TaskAssigner {
+  /**
+   * Sets assignment of a task command.
+   * @param command task command
+   * @return command with modified assignment information.
+   */
+  fun setAssignment(command: EngineTaskCommand): EngineTaskCommand
+}
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCollectorConfiguration.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCollectorConfiguration.kt
index 926e62ce4..9434e7272 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCollectorConfiguration.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCollectorConfiguration.kt
@@ -1,7 +1,13 @@
 package io.holunda.polyflow.taskpool.collector.task
 
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommandFilter
+import io.holunda.polyflow.taskpool.collector.CamundaTaskpoolCollectorConfiguration
 import io.holunda.polyflow.taskpool.collector.CamundaTaskpoolCollectorProperties
+import io.holunda.polyflow.taskpool.collector.TaskAssignerType
 import io.holunda.polyflow.taskpool.collector.TaskCollectorEnricherType
+import io.holunda.polyflow.taskpool.collector.task.assigner.EmptyTaskAssigner
+import io.holunda.polyflow.taskpool.collector.task.assigner.ProcessVariableChangeAssigningService
+import io.holunda.polyflow.taskpool.collector.task.assigner.ProcessVariablesTaskAssigner
 import io.holunda.polyflow.taskpool.collector.task.enricher.EmptyTaskCommandEnricher
 import io.holunda.polyflow.taskpool.collector.task.enricher.ProcessVariablesCorrelator
 import io.holunda.polyflow.taskpool.collector.task.enricher.ProcessVariablesFilter
@@ -14,12 +20,12 @@ import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
 import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
 import org.camunda.bpm.engine.spring.SpringProcessEnginePlugin
 import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties
+import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
 import org.springframework.context.ApplicationEventPublisher
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
-import org.springframework.context.annotation.Lazy
 
 /**
  * Constructs the task collector components.
@@ -48,29 +54,67 @@ class TaskCollectorConfiguration(
     }
   }
 
+  /**
+   * Task variables loader.
+   */
+  @Bean
+  fun taskVariablesLoader(
+    runtimeService: RuntimeService,
+    taskService: TaskService,
+    commandExecutor: CommandExecutor
+  ): TaskVariableLoader = TaskVariableLoader(runtimeService, taskService, commandExecutor)
+
   /**
    * Create enricher.
    */
   @Bean
   @ConditionalOnExpression("'\${polyflow.integration.collector.camunda.task.enricher.type}' != 'custom'")
   fun processVariablesEnricher(
-    runtimeService: RuntimeService,
-    taskService: TaskService,
-    commandExecutor: CommandExecutor,
+    taskVariableLoader: TaskVariableLoader,
     filter: ProcessVariablesFilter,
     correlator: ProcessVariablesCorrelator
   ): VariablesEnricher =
     when (camundaTaskpoolCollectorProperties.task.enricher.type) {
-      TaskCollectorEnricherType.processVariables -> ProcessVariablesTaskCommandEnricher(runtimeService, taskService, commandExecutor, filter, correlator)
+      TaskCollectorEnricherType.processVariables -> ProcessVariablesTaskCommandEnricher(
+        processVariablesFilter = filter,
+        processVariablesCorrelator = correlator,
+        taskVariableLoader = taskVariableLoader
+      )
+
       TaskCollectorEnricherType.no -> EmptyTaskCommandEnricher()
       else -> throw IllegalStateException("Could not initialize task enricher, used unknown ${camundaTaskpoolCollectorProperties.task.enricher.type} type.")
     }
 
+  /**
+   * Creates an empty task assigner if no assigner is defined.
+   */
+  @Bean
+  @ConditionalOnExpression("'\${polyflow.integration.collector.camunda.task.assigner.type}' != 'custom'")
+  fun taskAssigner(taskVariableLoader: TaskVariableLoader): TaskAssigner =
+    when (camundaTaskpoolCollectorProperties.task.assigner.type) {
+      TaskAssignerType.no -> EmptyTaskAssigner()
+      TaskAssignerType.processVariables -> ProcessVariablesTaskAssigner(
+        taskVariableLoader = taskVariableLoader,
+        processVariableTaskAssignerMapping = camundaTaskpoolCollectorProperties.task.assigner.toMapping()
+      )
+
+      else -> throw IllegalStateException("Could not initialize task assigner, used unknown ${camundaTaskpoolCollectorProperties.task.assigner.type} type.")
+    }
+
+  /**
+   * Service responsible for changing assignees on process variable change.
+   */
+  @Bean
+  @ConditionalOnExpression("'\${polyflow.integration.collector.camunda.task.assigner.type}' == 'process-variables' && '\${polyflow.integration.collector.camunda.process-variable.enabled}'")
+  fun processVariableChangeAssigningService(taskService: TaskService) = ProcessVariableChangeAssigningService(
+    taskService = taskService,
+    mapping = camundaTaskpoolCollectorProperties.task.assigner.toMapping()
+  )
 
   /**
    * Constructs the task collector service responsible for collecting Camunda Spring events and building commands out of them.
    */
-  @Bean
+  @Bean(TaskEventCollectorService.NAME)
   fun taskEventCollectorService(repositoryService: RepositoryService) = TaskEventCollectorService(
     camundaTaskpoolCollectorProperties = camundaTaskpoolCollectorProperties,
     repositoryService = repositoryService
@@ -82,9 +126,37 @@ class TaskCollectorConfiguration(
   @Bean
   fun taskCommandProcessor(
     engineTaskCommandSender: EngineTaskCommandSender,
-    variablesEnricher: VariablesEnricher
+    variablesEnricher: VariablesEnricher,
+    taskAssigner: TaskAssigner
   ) = TaskCommandProcessor(
     engineTaskCommandSender = engineTaskCommandSender,
-    enricher = variablesEnricher
+    enricher = variablesEnricher,
+    taskAssigner = taskAssigner
   )
+
+  /**
+   * Create a task collector service collecting tasks directly from the task service of the engine.
+   */
+  @Bean
+  @ConditionalOnProperty(value = ["polyflow.integration.collector.camunda.task.importer.enabled"], havingValue = "true", matchIfMissing = false)
+  fun taskServiceCollectorService(
+    taskService: TaskService,
+    commandExecutor: CommandExecutor,
+    applicationEventPublisher: ApplicationEventPublisher,
+    @Autowired(required = false) engineTaskCommandFilter: EngineTaskCommandFilter?
+  ): TaskServiceCollectorService {
+
+    if (engineTaskCommandFilter == null) {
+      CamundaTaskpoolCollectorConfiguration.logger.warn { "Task importer is configured, but no task filter is provided. All tasks commands will be rejected." }
+    }
+
+    return TaskServiceCollectorService(
+      taskService = taskService,
+      commandExecutor = commandExecutor,
+      camundaTaskpoolCollectorProperties = camundaTaskpoolCollectorProperties,
+      applicationEventPublisher = applicationEventPublisher,
+      engineTaskCommandFilter = engineTaskCommandFilter ?: object : EngineTaskCommandFilter {}
+    )
+
+  }
 }
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCommandProcessor.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCommandProcessor.kt
index ecf00f2c7..0aa86f415 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCommandProcessor.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskCommandProcessor.kt
@@ -14,7 +14,8 @@ import org.springframework.context.event.EventListener
  */
 class TaskCommandProcessor(
   private val engineTaskCommandSender: EngineTaskCommandSender,
-  private val enricher: VariablesEnricher
+  private val enricher: VariablesEnricher,
+  private val taskAssigner: TaskAssigner
 ) {
   companion object : KLogging()
 
@@ -27,9 +28,11 @@ class TaskCommandProcessor(
     when (command) {
       is TaskIdentityWithPayloadAndCorrelations -> enricher.enrich(command)
       else -> command
+    }.let {
+      taskAssigner.setAssignment(it)
     }.also { commandToSend ->
       if (logger.isTraceEnabled) {
-        logger.trace("COLLECTOR-008: Sending engine task command: $commandToSend.")
+        logger.trace {"COLLECTOR-008: Sending engine task command: $commandToSend." }
       }
       // enrich and send
       engineTaskCommandSender.send(commandToSend)
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
index 9f5776ba9..2978b57e2 100755
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
@@ -17,12 +17,13 @@ import org.springframework.core.annotation.Order
  * Collects Camunda events and Camunda historic events (event listener order is {@link TaskEventCollectorService#ORDER}) and emits Commands
  */
 class TaskEventCollectorService(
-  private val camundaTaskpoolCollectorProperties: CamundaTaskpoolCollectorProperties,
+  val camundaTaskpoolCollectorProperties: CamundaTaskpoolCollectorProperties, // must not be private to access in conditions of event handlers
   private val repositoryService: RepositoryService
 ) {
 
 
   companion object : KLogging() {
+    const val NAME = "taskEventCollectorService"
     // high order to be later than all other listeners and work on changed entity
     const val ORDER = Integer.MAX_VALUE - 100
   }
@@ -30,7 +31,7 @@ class TaskEventCollectorService(
   /**
    * Tracing of collector.
    */
-  @Order(ORDER)
+  @Order(ORDER - 10)
   @EventListener
   fun all(task: DelegateTask) {
     if (logger.isTraceEnabled) {
@@ -46,7 +47,7 @@ class TaskEventCollectorService(
    * Fires create command.
    */
   @Order(ORDER)
-  @EventListener(condition = "#task.eventName.equals('create')")
+  @EventListener(condition = "#task.eventName.equals('create') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectTaskEvent('create')")
   fun create(task: DelegateTask): CreateTaskCommand =
     CreateTaskCommand(
       id = task.id,
@@ -70,7 +71,7 @@ class TaskEventCollectorService(
    * Fires complete.
    */
   @Order(ORDER)
-  @EventListener(condition = "#task.eventName.equals('complete')")
+  @EventListener(condition = "#task.eventName.equals('complete') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectTaskEvent('complete')")
   fun complete(task: DelegateTask): CompleteTaskCommand =
     CompleteTaskCommand(
       id = task.id,
@@ -81,17 +82,21 @@ class TaskEventCollectorService(
    * Fires assign command.
    */
   @Order(ORDER)
-  @EventListener(condition = "#task.eventName.equals('assignment')")
+  @EventListener(condition = "#task.eventName.equals('assignment') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectTaskEvent('assignment')")
   fun assign(task: DelegateTask) {
     // this method is intentionally empty to demonstrate that the assign event is captured.
     // we hence rely on historic identity link events to capture assignment via API and via listeners more accurately.
+    // see implementation below
+    if (logger.isTraceEnabled) {
+      logger.trace { "Task ${task.id} is assigned to ${task.assignee}." }
+    }
   }
 
   /**
    * Fires delete command.
    */
   @Order(ORDER)
-  @EventListener(condition = "#task.eventName.equals('delete')")
+  @EventListener(condition = "#task.eventName.equals('delete') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectTaskEvent('delete')")
   fun delete(task: DelegateTask): DeleteTaskCommand =
     DeleteTaskCommand(
       id = task.id,
@@ -99,12 +104,11 @@ class TaskEventCollectorService(
       eventName = task.eventName
     )
 
-
   /**
    * Fires update command.
    */
   @Order(ORDER)
-  @EventListener(condition = "#task.eventName.equals('update')")
+  @EventListener(condition = "#task.eventName.equals('update') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectTaskEvent('update')")
   fun update(task: DelegateTask): UpdateAttributeTaskCommand? =
     if (task is TaskEntity) {
       if (task.isAssigneeChange()) {
@@ -123,10 +127,9 @@ class TaskEventCollectorService(
    * into the original intent.
    */
   @Order(ORDER)
-  @EventListener
-  fun update(changeEvent: HistoricTaskInstanceEventEntity): UpdateAttributesHistoricTaskCommand? =
-    when (changeEvent.eventType) {
-      "update" -> UpdateAttributesHistoricTaskCommand(
+  @EventListener(condition = "#changeEvent.eventType.equals('update') && @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectHistoryEvent('update')")
+  fun update(changeEvent: HistoricTaskInstanceEventEntity): UpdateAttributesHistoricTaskCommand =
+      UpdateAttributesHistoricTaskCommand(
         id = changeEvent.taskId,
         description = changeEvent.description,
         dueDate = changeEvent.dueDate,
@@ -138,15 +141,12 @@ class TaskEventCollectorService(
         sourceReference = changeEvent.sourceReference(repositoryService, camundaTaskpoolCollectorProperties.applicationName)
       )
 
-      else -> null
-    }
-
   /**
    * Fires update assignment historic command.
    * This is the only way to detect changes of identity links (candidate user/group change and remove).
    */
   @Order(ORDER)
-  @EventListener
+  @EventListener(condition = "@taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectHistoryEvent('add-identity-link') || @taskEventCollectorService.camundaTaskpoolCollectorProperties.task.collectHistoryEvent('delete-identity-link')")
   fun update(changeEvent: HistoricIdentityLinkLogEventEntity): Any? =
     when {
       // user assignment. Is needed because the assignment out of a listener is undetected otherwise.
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskServiceCollectorService.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskServiceCollectorService.kt
new file mode 100644
index 000000000..7265e7a8f
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskServiceCollectorService.kt
@@ -0,0 +1,85 @@
+package io.holunda.polyflow.taskpool.collector.task
+
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommandFilter
+import io.holunda.polyflow.taskpool.asCreateCommand
+import io.holunda.polyflow.taskpool.callInProcessEngineContext
+import io.holunda.polyflow.taskpool.collector.CamundaTaskpoolCollectorProperties
+import io.holunda.polyflow.view.query.PageableSortableQuery
+import org.camunda.bpm.engine.TaskService
+import org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager
+import org.camunda.bpm.engine.impl.interceptor.CommandContext
+import org.camunda.bpm.engine.impl.interceptor.CommandContextListener
+import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
+import org.camunda.bpm.engine.impl.persistence.entity.TaskEntity
+import org.springframework.context.ApplicationEventPublisher
+
+/**
+ * Service to collect tasks and fire the corresponding commands using Camunda Task Service.
+ */
+class TaskServiceCollectorService(
+  private val taskService: TaskService,
+  private val commandExecutor: CommandExecutor,
+  private val camundaTaskpoolCollectorProperties: CamundaTaskpoolCollectorProperties,
+  private val applicationEventPublisher: ApplicationEventPublisher,
+  private val engineTaskCommandFilter: EngineTaskCommandFilter
+) {
+
+  /**
+   * Collects tasks existing inm the Camunda task service
+   * sends commands for all which are not filtered away by the filter.
+   * In combination with [TaskAggregateEngineTaskCommandFilter] this can be used to initialize
+   * the event store with tasks from the engine.
+   * @param activeOnly parameter controlling if only active tasks are collected.
+   * @param firstResult first result of filtered command list to be sent
+   * @param maxResults last result of filtered command list to be sent
+   */
+  fun collectAndPopulateExistingTasks(activeOnly: Boolean = true, firstResult: Int = 0, maxResults: Int = 1000) {
+    callInProcessEngineContext(newContext = true) {
+      commandExecutor.execute { innerContext ->
+        innerContext.registerCommandContextListener(object : CommandContextListener {
+          override fun onCommandContextClose(commandContext: CommandContext) {
+            // Remove all cached entities from the inner context because we don't want to accidentally flush any changes that could cause
+            // OptimisticLockingExceptions in the outer context.
+            // (We don't change any variables but there are situations where _reading_ a variable also causes a change, e.g. when the serialized form of a
+            // complex variable has changed because properties have been added since creation of the variable. Another example: A variable containing a Set
+            // has been created and serialized from a LinkedHashSet with a specific order, but is deserialized to a HashSet with a different iteration order.
+            // When this is serialized again, the serialized form will look different because the order has changed.
+            (commandContext.sessions[DbEntityManager::class.java] as? DbEntityManager)?.dbEntityCache?.apply { cachedEntities.forEach { remove(it) } }
+          }
+
+          override fun onCommandFailed(commandContext: CommandContext, t: Throwable) {
+          }
+        })
+
+        // query
+        val engineTasks = taskService
+          .createTaskQuery()
+          .initializeFormKeys()
+          .let {
+            if (activeOnly) {
+              it.active()
+            } else {
+              it
+            }
+          }.list()
+
+        // create commands
+        val commands = engineTasks.filterIsInstance<TaskEntity>().map { task -> task.asCreateCommand(camundaTaskpoolCollectorProperties.applicationName) }
+
+        // filter and limit
+        val filtered = commands.filter { command -> engineTaskCommandFilter.test(command) }.let {
+          if (firstResult < it.size) {
+            it.subList(firstResult, maxResults.coerceAtMost(it.size))
+          } else {
+            it
+          }
+        }
+
+        // publish
+        filtered.forEach {
+          applicationEventPublisher.publishEvent(it)
+        }
+      }
+    }
+  }
+}
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskVariableLoader.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskVariableLoader.kt
new file mode 100644
index 000000000..78ec156b5
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskVariableLoader.kt
@@ -0,0 +1,89 @@
+package io.holunda.polyflow.taskpool.collector.task
+
+import io.holunda.camunda.taskpool.api.task.*
+import io.holunda.polyflow.taskpool.callInProcessEngineContext
+import mu.KLogging
+import org.camunda.bpm.engine.RuntimeService
+import org.camunda.bpm.engine.TaskService
+import org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager
+import org.camunda.bpm.engine.impl.interceptor.CommandContext
+import org.camunda.bpm.engine.impl.interceptor.CommandContextListener
+import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
+import org.camunda.bpm.engine.variable.VariableMap
+import org.camunda.bpm.engine.variable.Variables
+
+/**
+ * Facility to load variables in a command context.
+ * @param commandExecutor Camunda API executor.
+ * @param taskService Camunda API to access tasks.
+ * @param runtimeService Camunda API to access the execution.
+ */
+class TaskVariableLoader(
+  private val runtimeService: RuntimeService,
+  private val taskService: TaskService,
+  private val commandExecutor: CommandExecutor,
+) {
+  companion object : KLogging()
+
+  /**
+   * Retrieves typed variables from the context of a command.
+   * @param command command context.
+   * @return variable map.
+   */
+  fun <T : TaskIdentity> getTypeVariables(command: T): VariableMap {
+    return if (command.isHistoric()) {
+      // Task updated
+      // This is a historic command which is processed from a command context listener on command context close. Accessing variables at this point will try to add
+      // another command context listener (some camunda feature about updating mutable variables without explicitly saving them), which in turn causes a
+      // ConcurrentModificationException.
+      // We work around this by opening a new context.
+      // Caution: This will only work if the task is already flushed to the database, e.g. if it was created in a previous transaction.
+      // It will also see only the state of the variables that is flushed to the database
+      callInProcessEngineContext(newContext = true) {
+        commandExecutor.execute { innerContext ->
+          val task = taskService.createTaskQuery().taskId(command.id).singleResult()
+          if (task != null) {
+            taskService.getVariablesTyped(command.id)
+          } else {
+            val execution = runtimeService.createExecutionQuery().executionId(command.sourceReference.executionId).singleResult()
+            if (execution != null) {
+              runtimeService.getVariablesTyped(command.sourceReference.executionId)
+            } else {
+              logger.debug { "ENRICHER-004: Could not enrich variables from running execution ${command.sourceReference.executionId}, since it doesn't exist (anymore)." }
+              Variables.createVariables()
+            }
+          }.also {
+            innerContext.registerCommandContextListener(object : CommandContextListener {
+              override fun onCommandContextClose(commandContext: CommandContext) {
+                // Remove all cached entities from the inner context because we don't want to accidentally flush any changes that could cause
+                // OptimisticLockingExceptions in the outer context.
+                // (We don't change any variables but there are situations where _reading_ a variable also causes a change, e.g. when the serialized form of a
+                // complex variable has changed because properties have been added since creation of the variable. Another example: A variable containing a Set
+                // has been created and serialized from a LinkedHashSet with a specific order, but is deserialized to a HashSet with a different iteration order.
+                // When this is serialized again, the serialized form will look different because the order has changed.
+                (commandContext.sessions[DbEntityManager::class.java] as? DbEntityManager)?.dbEntityCache?.apply { cachedEntities.forEach { remove(it) } }
+              }
+
+              override fun onCommandFailed(commandContext: CommandContext, t: Throwable) {
+              }
+            })
+          }
+        }
+      }
+    } else {
+      // Create task
+      taskService.getVariablesTyped(command.id)
+    }
+  }
+}
+
+/**
+ * Checks if the command is created from a historic Camunda event.
+ */
+fun Any.isHistoric(): Boolean =
+  when (this) {
+    is CreateTaskCommand, is DeleteTaskCommand, is CompleteTaskCommand, is AssignTaskCommand, is UpdateAttributeTaskCommand -> false
+    is UpdateAttributesHistoricTaskCommand, is UpdateAssignmentTaskCommand -> true
+    else -> throw IllegalArgumentException("Unexpected command received: '$this'")
+  }
+
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/EmptyTaskAssigner.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/EmptyTaskAssigner.kt
new file mode 100644
index 000000000..61375a916
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/EmptyTaskAssigner.kt
@@ -0,0 +1,11 @@
+package io.holunda.polyflow.taskpool.collector.task.assigner
+
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommand
+import io.holunda.polyflow.taskpool.collector.task.TaskAssigner
+
+/**
+ * No-op task assigner changing nothing.
+ */
+class EmptyTaskAssigner : TaskAssigner {
+  override fun setAssignment(command: EngineTaskCommand): EngineTaskCommand = command
+}
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableChangeAssigningService.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableChangeAssigningService.kt
new file mode 100644
index 000000000..fc34058e8
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableChangeAssigningService.kt
@@ -0,0 +1,91 @@
+package io.holunda.polyflow.taskpool.collector.task.assigner
+
+import io.holunda.camunda.taskpool.api.task.*
+import io.holunda.polyflow.taskpool.sender.process.variable.CreateSingleProcessVariableCommand
+import io.holunda.polyflow.taskpool.sender.process.variable.UpdateSingleProcessVariableCommand
+import mu.KLogging
+import org.camunda.bpm.engine.TaskService
+import org.springframework.context.event.EventListener
+
+/**
+ * This service bridges the gap between changes in process variables and change of assignments of existing user tasks.
+ * It subscribes to changes to variables configures as assignment process variables and reacts to those changes
+ * emitting task assignment update events if the underlying process instance is waiting in a user task.
+ *
+ * In order to work properly, the collector needs to activate variable collection (even if the sender is disabled).
+ */
+class ProcessVariableChangeAssigningService(
+  private val taskService: TaskService,
+  private val mapping: ProcessVariableTaskAssignerMapping
+) {
+
+  companion object : KLogging()
+
+  /**
+   * React on new variables created.
+   */
+  @EventListener
+  fun on(command: CreateSingleProcessVariableCommand): EngineTaskCommand? {
+    // only relevant if waiting in a user task
+    val taskId = getTaskId(command.sourceReference) ?: return null
+    return when (command.variableName) {
+      mapping.assignee -> {
+        AssignTaskCommand(
+          id = taskId,
+          assignee = command.value.value.asStringValue()
+        )
+      }
+      mapping.candidateUsers -> {
+        if (command.value.value != null) {
+          AddCandidateUsersCommand(
+            id = taskId,
+            candidateUsers = command.value.value.asSetValue()
+          )
+        } else {
+          null
+        }
+      }
+      mapping.candidateGroups -> {
+        null
+      }
+
+      else -> null
+    }
+  }
+
+  /**
+   * React on variables updates.
+   */
+  @EventListener
+  fun on(command: UpdateSingleProcessVariableCommand): EngineTaskCommand? {
+    val taskId = getTaskId(command.sourceReference) ?: return null
+    return when (command.variableName) {
+      mapping.assignee -> {
+        AssignTaskCommand(
+          id = taskId,
+          assignee = command.value.value.asStringValue()
+        )
+      }
+      mapping.candidateUsers -> {
+        if (command.value.value != null) {
+          AddCandidateUsersCommand(
+            id = taskId,
+            candidateUsers = command.value.value.asSetValue()
+          )
+        } else {
+          null
+        }
+      }
+      mapping.candidateGroups -> {
+        null
+      }
+
+      else -> null
+    }
+  }
+
+
+  private fun getTaskId(sourceReference: SourceReference): String? {
+    return taskService.createTaskQuery().executionId(sourceReference.executionId).singleResult()?.id
+  }
+}
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableTaskAssignerMapping.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableTaskAssignerMapping.kt
new file mode 100644
index 000000000..ceb99db8c
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariableTaskAssignerMapping.kt
@@ -0,0 +1,73 @@
+package io.holunda.polyflow.taskpool.collector.task.assigner
+
+import org.camunda.bpm.engine.variable.VariableMap
+
+/**
+ * A mapping defining the names of process variables used for assignment.
+ */
+data class ProcessVariableTaskAssignerMapping(
+  val assignee: String?,
+  val candidateUsers: String?,
+  val candidateGroups: String?
+) {
+  /**
+   * Loads assignment from variables.
+   */
+  fun loadAssignmentFromVariables(variables: VariableMap): Assignment =
+    Assignment(
+      assignee = if (assignee != null && variables.containsKey(assignee)) {
+        variables[assignee].asStringValue()
+      } else {
+        null
+      },
+      candidateUsers = if (candidateUsers != null && variables.containsKey(candidateUsers)) {
+        variables[candidateUsers].asSetValue()
+      } else {
+        setOf()
+      },
+      candidateGroups = if (candidateGroups != null && variables.containsKey(candidateGroups)) {
+        variables[candidateGroups].asSetValue()
+      } else {
+        setOf()
+      }
+    )
+
+  /**
+   * Assignment information.
+   */
+  data class Assignment(
+    /**
+     * Assignee.
+     */
+    val assignee: String?,
+    /**
+     * Candidate users.
+     */
+    val candidateUsers: Set<String>,
+    /**
+     * Candidate groups.
+     */
+    val candidateGroups: Set<String>
+  )
+}
+
+/**
+ * Variable value to set or empty set.
+ */
+@Suppress("UNCHECKED_CAST")
+fun Any?.asSetValue() =
+  when (this) {
+    is Collection<*> -> (this as Collection<String>).toSet()
+    is String -> this.split(",").map { it.trim() }.toSet()
+    else -> setOf()
+  }
+
+/**
+ * Variable value to string or null.
+ */
+fun Any?.asStringValue() =
+  when (this) {
+    is String -> this
+    else -> null
+  }
+
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariablesTaskAssigner.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariablesTaskAssigner.kt
new file mode 100644
index 000000000..e61cc0249
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/assigner/ProcessVariablesTaskAssigner.kt
@@ -0,0 +1,44 @@
+package io.holunda.polyflow.taskpool.collector.task.assigner
+
+import io.holunda.camunda.taskpool.api.task.CreateTaskCommand
+import io.holunda.camunda.taskpool.api.task.EngineTaskCommand
+import io.holunda.polyflow.taskpool.collector.task.TaskAssigner
+import io.holunda.polyflow.taskpool.collector.task.TaskVariableLoader
+
+/**
+ * Task assigner retrieving assignment information from process variables.
+ */
+class ProcessVariablesTaskAssigner(
+  private val taskVariableLoader: TaskVariableLoader,
+  private val processVariableTaskAssignerMapping: ProcessVariableTaskAssignerMapping
+) : TaskAssigner {
+  override fun setAssignment(command: EngineTaskCommand): EngineTaskCommand =
+    when (command) {
+      is CreateTaskCommand -> processVariableTaskAssignerMapping
+        .loadAssignmentFromVariables(variables = taskVariableLoader.getTypeVariables(command))
+        .let { assignment ->
+          command.let {
+            if (assignment.assignee != null) {
+              it.copy(assignee = assignment.assignee)
+            } else {
+              it
+            }
+          }.let {
+            if (assignment.candidateUsers.isNotEmpty()) {
+              it.copy(candidateUsers = assignment.candidateUsers)
+            } else {
+              it
+            }
+          }.let {
+            if (assignment.candidateGroups.isNotEmpty()) {
+              it.copy(candidateGroups = assignment.candidateGroups)
+            } else {
+              it
+            }
+          }
+        }
+
+      else -> command
+    }
+
+}
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariableFilter.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariableFilter.kt
index 04c746d5c..ed513073a 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariableFilter.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariableFilter.kt
@@ -6,12 +6,12 @@ package io.holunda.polyflow.taskpool.collector.task.enricher
  * If a differentiation between individual user tasks of a process is required, use a {@link TaskVariableFilter} instead.
  */
 data class ProcessVariableFilter(
-        override val processDefinitionKey: ProcessDefinitionKey?,
-        val filterType: FilterType,
-        val processVariables: List<VariableName> = emptyList()
-): VariableFilter {
+  override val processDefinitionKey: ProcessDefinitionKey?,
+  val filterType: FilterType,
+  val processVariables: List<VariableName> = emptyList()
+) : VariableFilter {
 
-  constructor(filterType: FilterType, processVariables: List<VariableName>): this(null, filterType, processVariables)
+  constructor(filterType: FilterType, processVariables: List<VariableName>) : this(null, filterType, processVariables)
 
   override fun filter(taskDefinitionKey: TaskDefinitionKey, variableName: VariableName): Boolean {
     return (filterType == FilterType.INCLUDE) == processVariables.contains(variableName)
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariablesTaskCommandEnricher.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariablesTaskCommandEnricher.kt
index 704b18ab5..a71388750 100755
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariablesTaskCommandEnricher.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/ProcessVariablesTaskCommandEnricher.kt
@@ -2,6 +2,7 @@ package io.holunda.polyflow.taskpool.collector.task.enricher
 
 import io.holunda.camunda.taskpool.api.task.*
 import io.holunda.polyflow.taskpool.callInProcessEngineContext
+import io.holunda.polyflow.taskpool.collector.task.TaskVariableLoader
 import io.holunda.polyflow.taskpool.collector.task.VariablesEnricher
 import io.holunda.polyflow.taskpool.putAllTyped
 import mu.KLogging
@@ -16,76 +17,21 @@ import org.camunda.bpm.engine.variable.Variables.createVariables
 
 /**
  * Enriches commands with process variables.
- * @param runtimeService Camunda API to access the execution.
  * @param processVariablesFilter filter to whitelist or blacklist the variables which should be added to the task.
  */
 open class ProcessVariablesTaskCommandEnricher(
-  private val runtimeService: RuntimeService,
-  private val taskService: TaskService,
-  private val commandExecutor: CommandExecutor,
   private val processVariablesFilter: ProcessVariablesFilter,
   private val processVariablesCorrelator: ProcessVariablesCorrelator,
+  private val taskVariableLoader: TaskVariableLoader
 ) : VariablesEnricher {
 
   companion object : KLogging()
 
 
-  /**
-   * Retrieves typed variables to enrich current command.
-   * @param command command to enrich.
-   * @return variable map.
-   */
-  open fun <T : TaskIdentityWithPayloadAndCorrelations> getTypeVariables(command: T): VariableMap {
-    return if (command.isHistoric()) {
-      // Task updated
-      // This is a historic command which is processed from a command context listener on command context close. Accessing variables at this point will try to add
-      // another command context listener (some camunda feature about updating mutable variables without explicitly saving them), which in turn causes a
-      // ConcurrentModificationException.
-      // We work around this by opening a new context.
-      // Caution: This will only work if the task is already flushed to the database, e.g. if it was created in a previous transaction.
-      // It will also see only the state of the variables that is flushed to the database
-      callInProcessEngineContext(newContext = true) {
-        commandExecutor.execute { innerContext ->
-          val task = taskService.createTaskQuery().taskId(command.id).singleResult()
-          if (task != null) {
-            taskService.getVariablesTyped(command.id)
-          } else {
-            val execution = runtimeService.createExecutionQuery().executionId(command.sourceReference.executionId).singleResult()
-            if (execution != null) {
-              runtimeService.getVariablesTyped(command.sourceReference.executionId)
-            } else {
-              logger.debug { "ENRICHER-004: Could not enrich variables from running execution ${command.sourceReference.executionId}, since it doesn't exist (anymore)." }
-              createVariables()
-            }
-          }
-            .also {
-              innerContext.registerCommandContextListener(object : CommandContextListener {
-                override fun onCommandContextClose(commandContext: CommandContext) {
-                  // Remove all cached entities from the inner context because we don't want to accidentally flush any changes that could cause
-                  // OptimisticLockingExceptions in the outer context.
-                  // (We don't change any variables but there are situations where _reading_ a variable also causes a change, e.g. when the serialized form of a
-                  // complex variable has changed because properties have been added since creation of the variable. Another example: A variable containing a Set
-                  // has been created and serialized from a LinkedHashSet with a specific order, but is deserialized to a HashSet with a different iteration order.
-                  // When this is serialized again, the serialized form will look different because the order has changed.
-                  (commandContext.sessions[DbEntityManager::class.java] as? DbEntityManager)?.dbEntityCache?.apply { cachedEntities.forEach { remove(it) } }
-                }
-
-                override fun onCommandFailed(commandContext: CommandContext, t: Throwable) {
-                }
-              })
-            }
-        }
-      }
-    } else {
-      // Create task
-      taskService.getVariablesTyped(command.id)
-    }
-  }
-
   override fun <T : TaskIdentityWithPayloadAndCorrelations> enrich(command: T): T {
 
     // load variables typed
-    val variablesTyped = getTypeVariables(command)
+    val variablesTyped = taskVariableLoader.getTypeVariables(command)
 
     // Payload enrichment
     command.payload.putAllTyped(
@@ -111,17 +57,3 @@ open class ProcessVariablesTaskCommandEnricher(
   }
 }
 
-/**
- * Checks if the command is created from a historic Camunda event.
- */
-fun Any.isHistoric(): Boolean =
-  when (this) {
-    is CreateTaskCommand, is DeleteTaskCommand, is CompleteTaskCommand, is AssignTaskCommand, is UpdateAttributeTaskCommand -> false
-    is UpdateAttributesHistoricTaskCommand, is UpdateAssignmentTaskCommand -> true
-    else -> throw IllegalArgumentException("Unexpected command received: '$this'")
-  }
-
-
-
-
-
diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/VariableFilter.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/VariableFilter.kt
index e088cc9d9..abebd71fb 100644
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/VariableFilter.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/enricher/VariableFilter.kt
@@ -8,7 +8,7 @@ interface VariableFilter {
   val processDefinitionKey: ProcessDefinitionKey?
 
   /**
-   * Returns whether or not the process variable with the given name shall be contained in the payload of the given task.
+   * Returns whether the process variable with the given name shall be contained in the payload of the given task.
    * @param taskDefinitionKey the key of the task to be enriched
    * @param variableName the name of the process variable
    */
diff --git a/integration/camunda-bpm/taskpool-job-sender/pom.xml b/integration/camunda-bpm/taskpool-job-sender/pom.xml
index 1543cb71f..75e133f72 100755
--- a/integration/camunda-bpm/taskpool-job-sender/pom.xml
+++ b/integration/camunda-bpm/taskpool-job-sender/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-camunda-bpm-engine-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-camunda-bpm-taskpool-job-sender</artifactId>
@@ -33,7 +33,7 @@
       <artifactId>camunda-engine</artifactId>
       <scope>provided</scope>
     </dependency>
-    
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
@@ -121,11 +121,6 @@
       <artifactId>jackson-datatype-jsr310</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 
 </project>
diff --git a/integration/camunda-bpm/taskpool-job-sender/src/main/kotlin/io/holunda/polyflow/taskpool/sender/CamundaJobSenderConfiguration.kt b/integration/camunda-bpm/taskpool-job-sender/src/main/kotlin/io/holunda/polyflow/taskpool/sender/CamundaJobSenderConfiguration.kt
index 66686c300..cae2ddfa2 100644
--- a/integration/camunda-bpm/taskpool-job-sender/src/main/kotlin/io/holunda/polyflow/taskpool/sender/CamundaJobSenderConfiguration.kt
+++ b/integration/camunda-bpm/taskpool-job-sender/src/main/kotlin/io/holunda/polyflow/taskpool/sender/CamundaJobSenderConfiguration.kt
@@ -36,7 +36,6 @@ class CamundaJobSenderConfiguration(
     processEngineConfiguration: ProcessEngineConfigurationImpl,
     @Qualifier(COMMAND_BYTEARRAY_OBJECT_MAPPER)
     objectMapper: ObjectMapper,
-    commandListGateway: CommandListGateway,
     engineTaskCommandAccumulator: EngineTaskCommandAccumulator
   ): EngineTaskCommandSender =
     TxAwareAccumulatingCamundaJobEngineTaskCommandSender(
diff --git a/integration/common/datapool-sender/pom.xml b/integration/common/datapool-sender/pom.xml
index ad2fd105d..82c96800d 100755
--- a/integration/common/datapool-sender/pom.xml
+++ b/integration/common/datapool-sender/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-common-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-datapool-sender</artifactId>
diff --git a/integration/common/pom.xml b/integration/common/pom.xml
index e621ae3b5..95362a87c 100755
--- a/integration/common/pom.xml
+++ b/integration/common/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../../bom/parent/pom.xml</relativePath>
   </parent>
 
diff --git a/integration/common/tasklist-url-resolver/pom.xml b/integration/common/tasklist-url-resolver/pom.xml
index e665658b3..b70ce8c46 100644
--- a/integration/common/tasklist-url-resolver/pom.xml
+++ b/integration/common/tasklist-url-resolver/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-common-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-tasklist-url-resolver</artifactId>
diff --git a/integration/common/taskpool-sender/pom.xml b/integration/common/taskpool-sender/pom.xml
index d8b65aaca..f77540b22 100755
--- a/integration/common/taskpool-sender/pom.xml
+++ b/integration/common/taskpool-sender/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-common-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-taskpool-sender</artifactId>
diff --git a/integration/common/variable-serializer/pom.xml b/integration/common/variable-serializer/pom.xml
index 6afd3649f..ccbdc94b8 100755
--- a/integration/common/variable-serializer/pom.xml
+++ b/integration/common/variable-serializer/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-integration-common-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-variable-serializer</artifactId>
diff --git a/pom.xml b/pom.xml
index 3ca475cc9..563665431 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 
   <groupId>io.holunda.polyflow</groupId>
   <artifactId>polyflow-root</artifactId>
-  <version>3.8.3-SNAPSHOT</version>
+  <version>3.14.4-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>POM: ${project.artifactId}</name>
@@ -20,7 +20,7 @@
     <version.java>${java.version}</version.java>
     <version.java>${java.version}</version.java>
 
-    <kotlin.version>1.8.20</kotlin.version>
+    <kotlin.version>1.9.10</kotlin.version>
     <kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
     <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
     <kotlin-logging.version>3.0.5</kotlin-logging.version>
@@ -41,13 +41,13 @@
           <!-- cleaning -->
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-clean-plugin</artifactId>
-          <version>3.2.0</version>
+          <version>3.3.1</version>
         </plugin>
         <!-- gitflow -->
         <plugin>
           <groupId>com.amashchenko.maven.plugin</groupId>
           <artifactId>gitflow-maven-plugin</artifactId>
-          <version>1.19.0</version>
+          <version>1.20.0</version>
           <configuration>
             <gitFlowConfig>
               <productionBranch>master</productionBranch>
@@ -67,7 +67,7 @@
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>versions-maven-plugin</artifactId>
-          <version>2.15.0</version>
+          <version>2.16.0</version>
           <configuration>
             <generateBackupPoms>false</generateBackupPoms>
           </configuration>
@@ -79,7 +79,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-enforcer-plugin</artifactId>
-        <version>3.3.0</version>
+        <version>3.4.1</version>
         <executions>
           <execution>
             <id>enforce-maven</id>
@@ -159,7 +159,7 @@
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-gpg-plugin</artifactId>
-            <version>3.0.1</version>
+            <version>3.1.0</version>
             <configuration>
               <!-- pass -Dgpg.keyname=.. and -Dgpg.passphrase=... -->
               <gpgArguments>
diff --git a/view/form-url-resolver/pom.xml b/view/form-url-resolver/pom.xml
index fc5d1b4af..67805e164 100644
--- a/view/form-url-resolver/pom.xml
+++ b/view/form-url-resolver/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-form-url-resolver</artifactId>
diff --git a/view/jpa/pom.xml b/view/jpa/pom.xml
index 7eb3a252d..50675ed86 100644
--- a/view/jpa/pom.xml
+++ b/view/jpa/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-view-jpa</artifactId>
@@ -51,7 +51,6 @@
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
-      <version>1.4.200</version> <!-- pin -->
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -103,6 +102,12 @@
       <groupId>org.axonframework</groupId>
       <artifactId>axon-spring-boot-starter</artifactId>
       <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.axonframework</groupId>
+          <artifactId>axon-server-connector</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
   </dependencies>
 
@@ -228,7 +233,7 @@
               <dependency>
                 <groupId>com.microsoft.sqlserver</groupId>
                 <artifactId>mssql-jdbc</artifactId>
-                <version>12.2.0.jre11</version>
+                <version>12.4.1.jre11</version>
               </dependency>
               <dependency>
                 <groupId>org.postgresql</groupId>
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt
index 300fd4156..ca56faa6e 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt
@@ -15,6 +15,7 @@ import io.holunda.polyflow.view.jpa.data.toDataEntry
 import io.holunda.polyflow.view.jpa.task.TaskEntity
 import io.holunda.polyflow.view.jpa.task.TaskRepository
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasApplication
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAssignedTo
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAssigneeSet
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAuthorizedFor
 import io.holunda.polyflow.view.jpa.task.toEntity
@@ -55,12 +56,27 @@ class JpaPolyflowViewTaskService(
     val criteria = toCriteria(query.filters)
     val taskSpecification = criteria.toTaskSpecification()
     val dataEntrySpecification = criteria.toDataEntrySpecification()
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+
+    val pageRequest = pageRequest(query.page, query.size, sort)
+
+    val userQuery = if (query.assignedToMeOnly) {
+      isAssignedTo(query.user.username)
+    } else {
+      composeOr(
+        listOf(
+          isAuthorizedFor(authorizedPrincipals),
+          isAssignedTo(query.user.username)
+        )
+      )
+    }
 
     val page = if (taskSpecification != null) {
-      taskRepository.findAll(taskSpecification.and(isAuthorizedFor(authorizedPrincipals)), pageRequest)
+      taskRepository.findAll(taskSpecification.and(userQuery), pageRequest)
     } else {
-      taskRepository.findAll(isAuthorizedFor(authorizedPrincipals), pageRequest)
+      taskRepository.findAll(userQuery, pageRequest)
     }
       .map { taskEntity ->
         TaskWithDataEntries(
@@ -103,7 +119,10 @@ class JpaPolyflowViewTaskService(
     val criteria = toCriteria(query.filters)
     val taskSpecification = criteria.toTaskSpecification()
     val dataEntrySpecification = criteria.toDataEntrySpecification()
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
 
     val page = if (taskSpecification != null) {
       taskRepository.findAll(taskSpecification.and(taskAuthorizationSpecification), pageRequest)
@@ -142,8 +161,10 @@ class JpaPolyflowViewTaskService(
     val criteria = toCriteria(query.filters)
     val taskSpecification = criteria.toTaskSpecification()
     val dataEntrySpecification = criteria.toDataEntrySpecification()
-
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
 
     val page = if (taskSpecification != null) {
       taskRepository.findAll(taskSpecification, pageRequest)
@@ -180,12 +201,25 @@ class JpaPolyflowViewTaskService(
   override fun query(query: TasksForUserQuery): TaskQueryResult {
     val authorizedPrincipals: Set<AuthorizationPrincipal> = setOf(user(query.user.username)).plus(query.user.groups.map { group(it) })
     val specification = toCriteria(query.filters).toTaskSpecification()
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
+    val userQuery = if (query.assignedToMeOnly) {
+      isAssignedTo(query.user.username)
+    } else {
+      composeOr(
+        listOf(
+          isAuthorizedFor(authorizedPrincipals),
+          isAssignedTo(query.user.username)
+        )
+      )
+    }
 
     val page = if (specification != null) {
-      taskRepository.findAll(specification.and(isAuthorizedFor(authorizedPrincipals)), pageRequest)
+      taskRepository.findAll(specification.and(userQuery), pageRequest)
     } else {
-      taskRepository.findAll(isAuthorizedFor(authorizedPrincipals), pageRequest)
+      taskRepository.findAll(userQuery, pageRequest)
     }.map { taskEntity -> taskEntity.toTask(objectMapper) }
 
     return TaskQueryResult(
@@ -203,7 +237,41 @@ class JpaPolyflowViewTaskService(
       )
     )
     val taskSpecification = toCriteria(query.filters).toTaskSpecification()
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
+
+    val page = if (taskSpecification != null) {
+      taskRepository.findAll(taskSpecification.and(authorizationSpecification), pageRequest)
+    } else {
+      taskRepository.findAll(authorizationSpecification, pageRequest)
+    }.map { taskEntity -> taskEntity.toTask(objectMapper) }
+
+    return TaskQueryResult(
+      elements = page.toList(),
+      totalElementCount = page.totalElements.toInt()
+    )
+  }
+
+  @QueryHandler
+  override fun query(query: TasksForCandidateUserAndGroupQuery): TaskQueryResult {
+    val authorizationSpecification =
+      composeAnd(
+        listOf(
+          composeOr(
+            listOf(
+              isAuthorizedFor(setOf(user(query.user.username)).plus(query.user.groups.map { group(it) }.toSet())),
+            )
+          ),
+          isAssigneeSet(query.includeAssigned)
+        )
+      )
+    val taskSpecification = toCriteria(query.filters).toTaskSpecification()
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
 
     val page = if (taskSpecification != null) {
       taskRepository.findAll(taskSpecification.and(authorizationSpecification), pageRequest)
@@ -220,7 +288,10 @@ class JpaPolyflowViewTaskService(
   @QueryHandler
   override fun query(query: AllTasksQuery): TaskQueryResult {
     val specification = toCriteria(query.filters).toTaskSpecification()
-    val pageRequest = pageRequest(query.page, query.size, query.sort)
+    val sort = query.apply {
+      sanitizeSort(Task::class)
+    }.mapTaskSort()
+    val pageRequest = pageRequest(query.page, query.size, sort)
     val page = if (specification != null) {
       taskRepository.findAll(specification, pageRequest)
     } else {
@@ -302,7 +373,7 @@ class JpaPolyflowViewTaskService(
           event.toEntity(
             objectMapper,
             polyflowJpaViewProperties.payloadAttributeLevelLimit,
-            polyflowJpaViewProperties.dataEntryJsonPathFilters()
+            polyflowJpaViewProperties.taskJsonPathFilters()
           )
         )
         emitTaskUpdate(updated)
@@ -396,7 +467,7 @@ class JpaPolyflowViewTaskService(
             objectMapper,
             entity,
             polyflowJpaViewProperties.payloadAttributeLevelLimit,
-            polyflowJpaViewProperties.dataEntryJsonPathFilters()
+            polyflowJpaViewProperties.taskJsonPathFilters()
           )
         )
         emitTaskUpdate(updated)
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/PolyflowJpaViewProperties.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/PolyflowJpaViewProperties.kt
index a2d808874..e9ce9b051 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/PolyflowJpaViewProperties.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/PolyflowJpaViewProperties.kt
@@ -26,7 +26,14 @@ data class PolyflowJpaViewProperties(
    * Filters for the paths for indexing.
    */
   @NestedConfigurationProperty
-  private val dataEntryFilters: PayloadAttributeFilterPaths = PayloadAttributeFilterPaths()
+  private val dataEntryFilters: PayloadAttributeFilterPaths = PayloadAttributeFilterPaths(),
+
+  /**
+   * Filters for the path for indexing.
+   */
+  @NestedConfigurationProperty
+  private val taskFilters: PayloadAttributeFilterPaths = PayloadAttributeFilterPaths()
+
 ) {
   /**
    * Extracts JSON path filters out of the properties.
@@ -34,6 +41,14 @@ data class PolyflowJpaViewProperties(
   fun dataEntryJsonPathFilters(): List<Pair<JsonPathFilterFunction, FilterType>> {
     return this.dataEntryFilters.include.map { eqInclude(it) }.plus(this.dataEntryFilters.exclude.map { eqExclude(it) })
   }
+
+  /**
+   * Extracts JSON path filters out of the properties.
+   */
+  fun taskJsonPathFilters(): List<Pair<JsonPathFilterFunction, FilterType>> {
+    return this.taskFilters.include.map { eqInclude(it) }.plus(this.taskFilters.exclude.map { eqExclude(it) })
+  }
+
 }
 
 /**
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/SpecificationExt.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/SpecificationExt.kt
index 8cb6747b1..81a3b9081 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/SpecificationExt.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/SpecificationExt.kt
@@ -3,9 +3,8 @@ package io.holunda.polyflow.view.jpa
 import io.holunda.camunda.taskpool.api.business.DataEntryState
 import io.holunda.camunda.taskpool.api.business.ProcessingType
 import io.holunda.polyflow.view.DataEntry
-import io.holunda.polyflow.view.filter.Criterion
-import io.holunda.polyflow.view.filter.EQUALS
-import io.holunda.polyflow.view.filter.LIKE
+import io.holunda.polyflow.view.Task
+import io.holunda.polyflow.view.filter.*
 import io.holunda.polyflow.view.jpa.data.DataEntryEntity
 import io.holunda.polyflow.view.jpa.data.DataEntryRepository.Companion.hasDataEntryPayloadAttribute
 import io.holunda.polyflow.view.jpa.data.DataEntryRepository.Companion.hasEntryId
@@ -15,14 +14,27 @@ import io.holunda.polyflow.view.jpa.data.DataEntryRepository.Companion.hasState
 import io.holunda.polyflow.view.jpa.data.DataEntryRepository.Companion.hasType
 import io.holunda.polyflow.view.jpa.task.TaskEntity
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasBusinessKey
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDate
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDateAfter
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDateBefore
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDate
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDateAfter
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDateBefore
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasPriority
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasProcessName
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasTaskPayloadAttribute
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeBusinessKey
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeDescription
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeName
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeProcessName
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeTextSearch
+import io.holunda.polyflow.view.query.PageableSortableQuery
 import org.springframework.data.domain.PageRequest
 import org.springframework.data.domain.Sort
+import org.springframework.data.domain.Sort.Direction
 import org.springframework.data.jpa.domain.Specification
 import org.springframework.data.jpa.domain.Specification.where
+import java.time.Instant
 
 /**
  * Creates a JPQL specification out of predicate wrapper.
@@ -66,7 +78,12 @@ fun pageRequest(page: Int, size: Int, sort: String?): PageRequest {
   val sortCriteria = if (sort.isNullOrBlank()) {
     null
   } else {
-    Sort.by(Sort.Direction.fromOptionalString(sort.substring(0, 1)).orElse(Sort.DEFAULT_DIRECTION), sort.substring(1))
+    val direction = if (sort.substring(0, 1) == "+") {
+      Direction.ASC
+    } else {
+      Direction.DESC
+    }
+    Sort.by(direction, sort.substring(1))
   }
   return if (sortCriteria != null) {
     PageRequest.of(page, size, sortCriteria)
@@ -75,37 +92,81 @@ fun pageRequest(page: Int, size: Int, sort: String?): PageRequest {
   }
 }
 
+/**
+ * Map sort string from the view API to implementation sort of the entities.
+ */
+fun PageableSortableQuery.mapTaskSort(): String {
+  return if (this.sort == null) {
+    // no sort is specified, we don't want unsorted results.
+    "-${TaskEntity::createdDate.name}"
+  } else {
+    val direction = sort!!.substring(0, 1)
+    val field = sort!!.substring(1)
+    return when (field) {
+      Task::name.name -> TaskEntity::name.name
+      Task::description.name -> TaskEntity::description.name
+      Task::assignee.name -> TaskEntity::assignee.name
+      Task::createTime.name -> TaskEntity::createdDate.name
+      Task::dueDate.name -> TaskEntity::dueDate.name
+      Task::followUpDate.name -> TaskEntity::followUpDate.name
+      Task::owner.name -> TaskEntity::owner.name
+      Task::priority.name -> TaskEntity::priority.name
+      Task::formKey.name -> TaskEntity::formKey.name
+      Task::businessKey.name -> TaskEntity::businessKey.name
+      Task::id.name -> TaskEntity::taskId.name
+      Task::taskDefinitionKey.name -> TaskEntity::taskDefinitionKey.name
+      else -> throw IllegalArgumentException("'$field' is not supported for sorting in JPA View")
+    }.let { "$direction$it" }
+  }
+}
+
 
 /**
  * Specification for query on task attributes.
  */
 internal fun List<Criterion>.toTaskAttributeSpecification(): Specification<TaskEntity>? {
-  val relevant = this.filterIsInstance<Criterion.TaskCriterion>().map { it.toTaskSpecification() }
-  return composeAnd(relevant)
+  val relevant = this.filterIsInstance<Criterion.TaskCriterion>()
+  // compose criteria with same name with OR and criteria with different names with AND
+  val relevantByName = relevant.groupBy { it.name }
+  val orComposedByName = relevantByName.map { (_, criteria) -> composeOr(criteria.map { it.toTaskSpecification() }) }
+
+  return composeAnd(orComposedByName)
 }
 
 /**
  * Specification for query on data entry attributes.
  */
 internal fun List<Criterion>.toDataEntryAttributeSpecification(): Specification<DataEntryEntity>? {
-  val relevant = this.filterIsInstance<Criterion.DataEntryCriterion>().map { it.toDataEntrySpecification() }
-  return composeAnd(relevant)
+  val relevant = this.filterIsInstance<Criterion.DataEntryCriterion>()
+  // compose criteria with same name with OR and criteria with different names with AND
+  val relevantByName = relevant.groupBy { it.name }
+  val orComposedByName = relevantByName.map { (_, criteria) -> composeOr(criteria.map { it.toDataEntrySpecification() }) }
+
+  return composeAnd(orComposedByName)
 }
 
 /**
  * Specification on payload.
  */
 internal fun List<Criterion>.toDataEntryPayloadSpecification(): Specification<DataEntryEntity>? {
-  val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>().map { it.toDataEntrySpecification() }
-  return composeAnd(relevant)
+  val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>()
+  // compose criteria with same name with OR and criteria with different names with AND
+  val relevantByName = relevant.groupBy { it.name }
+  val orComposedByName = relevantByName.map { (_, criteria) -> criteria.toOrDataEntrySpecification() }
+
+  return composeAnd(orComposedByName)
 }
 
 /**
  * Specification on payload.
  */
 internal fun List<Criterion>.toTaskPayloadSpecification(): Specification<TaskEntity>? {
-  val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>().map { it.toTaskSpecification() }
-  return composeAnd(relevant)
+  val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>()
+  // compose criteria with same name with OR and criteria with different names with AND
+  val relevantByName = relevant.groupBy { it.name }
+  val orComposedByName = relevantByName.map { (_, criteria) -> criteria.toOrTaskSpecification() }
+
+  return composeAnd(orComposedByName)
 }
 
 
@@ -116,30 +177,56 @@ internal fun Criterion.TaskCriterion.toTaskSpecification(): Specification<TaskEn
   return when (this.operator) {
     EQUALS -> {
       when (this.name) {
-        TaskEntity::businessKey.name -> hasBusinessKey(this.value)
+        "processName" -> hasProcessName(this.value)
+        Task::businessKey.name -> hasBusinessKey(this.value)
+        Task::dueDate.name -> hasDueDate(Instant.parse(this.value))
+        Task::followUpDate.name -> hasFollowUpDate(Instant.parse(this.value))
+        Task::priority.name -> hasPriority(this.value.toInt())
         else -> throw IllegalArgumentException("JPA View found unsupported task attribute for equals comparison: ${this.name}.")
       }
     }
+
     LIKE -> {
       when (this.name) {
-        TaskEntity::name.name -> likeName(this.value)
-        TaskEntity::description.name -> likeDescription(this.value)
-        TaskEntity::businessKey.name -> likeBusinessKey(this.value)
+        "textSearch" -> likeTextSearch(this.value)
+        Task::name.name -> likeName(this.value)
+        Task::description.name -> likeDescription(this.value)
+        "processName" -> likeProcessName(this.value)
+        Task::businessKey.name -> likeBusinessKey(this.value)
         else -> throw IllegalArgumentException("JPA View found unsupported task attribute for like comparison: ${this.name}.")
       }
     }
+
+    LESS -> {
+      when (this.name) {
+        Task::dueDate.name -> hasDueDateBefore(Instant.parse(this.value))
+        Task::followUpDate.name -> hasFollowUpDateBefore(Instant.parse(this.value))
+        else -> throw IllegalArgumentException("JPA View found unsupported task attribute for < comparison: ${this.name}.")
+      }
+    }
+
+    GREATER -> {
+      when (this.name) {
+        Task::dueDate.name -> hasDueDateAfter(Instant.parse(this.value))
+        Task::followUpDate.name -> hasFollowUpDateAfter(Instant.parse(this.value))
+        else -> throw IllegalArgumentException("JPA View found unsupported task attribute for > comparison: ${this.name}.")
+      }
+    }
+
     else -> throw IllegalArgumentException("JPA View found unsupported comparison ${this.operator} for attribute ${this.name}.")
   }
 }
 
 /**
- * Creates JPA Specification for query of payload attributes based on JSON paths.
+ * Creates JPA Specification for query of payload attributes based on JSON paths. All criteria must have the same path
+ * and will be composed by the logical OR operator.
  */
-internal fun Criterion.PayloadEntryCriterion.toTaskSpecification(): Specification<TaskEntity> {
-  return when (this.operator) {
-    EQUALS -> hasTaskPayloadAttribute(this.name, this.value)
-    else -> throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
-  }
+internal fun List<Criterion.PayloadEntryCriterion>.toOrTaskSpecification(): Specification<TaskEntity> {
+  require(this.isNotEmpty()) { "List of criteria must not be empty." }
+  require(this.all { it.operator == EQUALS }) { "JPA View currently supports only equals as operator for filtering of payload attributes." }
+  require(this.distinctBy { it.name }.size == 1) { "All criteria must have the same path." }
+
+  return hasTaskPayloadAttribute(this.first().name, this.map { it.value })
 }
 
 /**
@@ -157,13 +244,15 @@ internal fun Criterion.DataEntryCriterion.toDataEntrySpecification(): Specificat
 }
 
 /**
- * Creates JPA Specification for query of payload attributes based on JSON paths.
+ * Creates JPA Specification for query of payload attributes based on JSON paths. All criteria must have the same path
+ * and will be composed by the logical OR operator.
  */
-internal fun Criterion.PayloadEntryCriterion.toDataEntrySpecification(): Specification<DataEntryEntity> {
-  return when (this.operator) {
-    EQUALS -> hasDataEntryPayloadAttribute(this.name, this.value)
-    else -> throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
-  }
+internal fun List<Criterion.PayloadEntryCriterion>.toOrDataEntrySpecification(): Specification<DataEntryEntity> {
+  require(this.isNotEmpty()) { "List of criteria must not be empty." }
+  require(this.all { it.operator == EQUALS }) { "JPA View currently supports only equals as operator for filtering of payload attributes." }
+  require(this.distinctBy { it.name }.size == 1) { "All criteria must have the same path." }
+
+  return hasDataEntryPayloadAttribute(this.first().name, this.map { it.value })
 }
 
 
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryEntity.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryEntity.kt
index 23b38f495..d37f65b96 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryEntity.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryEntity.kt
@@ -2,6 +2,8 @@ package io.holunda.polyflow.view.jpa.data
 
 
 import io.holunda.polyflow.view.jpa.payload.PayloadAttribute
+import org.hibernate.annotations.Fetch
+import org.hibernate.annotations.FetchMode
 import java.time.Instant
 import javax.persistence.*
 
@@ -13,21 +15,19 @@ import javax.persistence.*
 class DataEntryEntity(
   @EmbeddedId
   var dataEntryId: DataEntryId,
-  @Column(name = "TYPE", nullable = false)
+  @Column(name = "TYPE", length = 255, nullable = false)
   var type: String,
-  @Column(name = "NAME", nullable = false)
-  @Lob
+  @Column(name = "NAME", length = 255, nullable = false)
   var name: String,
-  @Column(name = "APPLICATION_NAME", nullable = false)
+  @Column(name = "APPLICATION_NAME", length = 64, nullable = false)
   var applicationName: String,
-  @Column(name = "FORM_KEY")
+  @Column(name = "FORM_KEY", length = 64, nullable = true)
   var formKey: String? = null,
   @Column(name = "REVISION")
   var revision: Long = 0L,
   @Embedded
   var state: DataEntryStateEmbeddable,
-  @Column(name = "DESCRIPTION")
-  @Lob
+  @Column(name = "DESCRIPTION", nullable = true, length = 2048)
   var description: String? = null,
 
   @Column(name = "DATE_CREATED", nullable = false)
@@ -60,6 +60,7 @@ class DataEntryEntity(
 
 
   @OneToMany(mappedBy = "dataEntry", orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
+  @Fetch(FetchMode.SELECT)
   var protocol: MutableList<ProtocolElement> = mutableListOf(),
 
   @Column(name = "PAYLOAD")
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryId.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryId.kt
index a6e143968..3228cde99 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryId.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryId.kt
@@ -10,9 +10,9 @@ import javax.persistence.Embeddable
  */
 @Embeddable
 class DataEntryId(
-  @Column(name = "ENTRY_ID", nullable = false)
+  @Column(name = "ENTRY_ID", length = 64, nullable = false)
   var entryId: String,
-  @Column(name = "ENTRY_TYPE", nullable = false)
+  @Column(name = "ENTRY_TYPE", length = 128, nullable = false)
   var entryType: String
 ) : Serializable {
 
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepository.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepository.kt
index 1c6380ae2..2d7354686 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepository.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepository.kt
@@ -84,20 +84,26 @@ interface DataEntryRepository : CrudRepository<DataEntryEntity, DataEntryId>, Jp
       }
 
     /**
-     * Specification for checking the payload attribute.
+     * Specification for checking the payload attribute. If multiple values are given, one of them must match.
+     * payload.name = ? AND (payload.value = ? OR payload.value = ? OR ...)
      */
-    fun hasDataEntryPayloadAttribute(name: String, value: String): Specification<DataEntryEntity> =
-      Specification { dataEntry, _, builder ->
+    fun hasDataEntryPayloadAttribute(name: String, values: List<String>): Specification<DataEntryEntity> =
+      Specification { dataEntry, query, builder ->
+        query.distinct(true)
         val join = dataEntry.join<DataEntryEntity, Set<PayloadAttribute>>(DataEntryEntity::payloadAttributes.name)
         val pathEquals = builder.equal(
           join.get<String>(PayloadAttribute::path.name),
           name
         )
-        val valueEquals = builder.equal(
-          join.get<String>(PayloadAttribute::value.name),
-          value
-        )
-        builder.and(pathEquals, valueEquals)
+
+        val valueAnyOf = values.map {
+          builder.equal(
+            join.get<String>(PayloadAttribute::value.name),
+            it
+          )
+        }.let { builder.or(*it.toTypedArray()) }
+
+        builder.and(pathEquals, valueAnyOf)
       }
 
     /**
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryStateEmbeddable.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryStateEmbeddable.kt
index 8a9ef3e05..12e5cd1e3 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryStateEmbeddable.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryStateEmbeddable.kt
@@ -10,9 +10,9 @@ import javax.persistence.Embeddable
  */
 @Embeddable
 class DataEntryStateEmbeddable(
-  @Column(name = "PROCESSING_TYPE", nullable = false)
+  @Column(name = "PROCESSING_TYPE", length = 64, nullable = false)
   var processingType: String,
-  @Column(name = "STATE", nullable = false)
+  @Column(name = "STATE", length = 64, nullable = false)
   var state: String
 ) : Serializable {
   companion object {
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/ProtocolElement.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/ProtocolElement.kt
index 7b2de164f..129b45975 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/ProtocolElement.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/data/ProtocolElement.kt
@@ -11,18 +11,18 @@ import javax.persistence.*
 @Table(name = "PLF_DATA_ENTRY_PROTOCOL")
 class ProtocolElement(
   @Id
-  @Column(name = "ID")
+  @Column(name = "ID", length = 64, nullable = false)
   var id: String = UUID.randomUUID().toString(),
 
   @Column(name = "TIME", nullable = false)
   var time: Instant = Instant.now(),
   @Embedded
   var state: DataEntryStateEmbeddable,
-  @Column(name = "USERNAME", nullable = true)
+  @Column(name = "USERNAME", length = 64, nullable = true)
   var username: String? = null,
-  @Column(name = "LOG_MESSAGE", nullable = true)
+  @Column(name = "LOG_MESSAGE", length = 2048, nullable = true)
   var logMessage: String? = null,
-  @Column(name = "LOG_DETAILS", nullable = true)
+  @Column(name = "LOG_DETAILS", length = 2048, nullable = true)
   var logDetails: String? = null,
 
   @ManyToOne
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionEntity.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionEntity.kt
index d723e1d86..f1b9fc495 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionEntity.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionEntity.kt
@@ -9,20 +9,19 @@ import javax.persistence.*
 @Table(name = "PLF_PROC_DEF")
 class ProcessDefinitionEntity(
   @Id
-  @Column(name = "PROC_DEF_ID")
+  @Column(name = "PROC_DEF_ID", length = 255)
   var processDefinitionId: String,
-  @Column(name = "PROC_DEF_KEY", nullable = false)
+  @Column(name = "PROC_DEF_KEY", length = 255, nullable = false)
   var processDefinitionKey: String,
   @Column(name = "PROC_DEF_VERSION", nullable = false)
   var processDefinitionVersion: Int,
-  @Column(name = "APPLICATION_NAME", nullable = false)
+  @Column(name = "APPLICATION_NAME", length = 64, nullable = false)
   var applicationName: String,
-  @Column(name = "NAME", nullable = false)
+  @Column(name = "NAME", length = 255, nullable = false)
   var name: String,
   @Column(name = "VERSION_TAG")
   var versionTag: String? = null,
-  @Column(name = "DESCRIPTION")
-  @Lob
+  @Column(name = "DESCRIPTION", length = 2048)
   var description: String? = null,
   @Column(name = "START_FORM_KEY")
   var startFormKey: String? = null,
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceEntity.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceEntity.kt
index c3d5222e8..95e38d83a 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceEntity.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceEntity.kt
@@ -10,23 +10,23 @@ import javax.persistence.*
 @Table(name = "PLF_PROC_INSTANCE")
 class ProcessInstanceEntity(
   @Id
-  @Column(name = "INSTANCE_ID", nullable = true)
+  @Column(name = "INSTANCE_ID", length = 64, nullable = true)
   var processInstanceId: String,
-  @Column(name = "BUSINESS_KEY", nullable = true)
+  @Column(name = "BUSINESS_KEY", length = 64, nullable = true)
   var businessKey: String?,
-  @Column(name = "SUPER_INSTANCE_ID", nullable = true)
+  @Column(name = "SUPER_INSTANCE_ID", length = 64, nullable = true)
   var superInstanceId: String?,
-  @Column(name = "START_ACTIVITY_ID", nullable = true)
+  @Column(name = "START_ACTIVITY_ID", length = 64, nullable = true)
   var startActivityId: String?,
-  @Column(name = "END_ACTIVITY_ID", nullable = true)
+  @Column(name = "END_ACTIVITY_ID", length = 64, nullable = true)
   var endActivityId: String?,
-  @Column(name = "DELETE_REASON", nullable = true)
+  @Column(name = "DELETE_REASON", length = 2048, nullable = true)
   var deleteReason: String?,
-  @Column(name = "START_USER_ID", nullable = true)
+  @Column(name = "START_USER_ID", length = 64, nullable = true)
   var startUserId: String?,
   @Embedded
   var sourceReference: SourceReferenceEmbeddable,
-  @Column(name = "RUN_STATE", nullable = false)
+  @Column(name = "RUN_STATE", length = 64, nullable = false)
   @Enumerated(EnumType.STRING)
   var state: ProcessInstanceState
 )
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/SourceReferenceEmbeddable.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/SourceReferenceEmbeddable.kt
index 72dddecbc..50f177bba 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/SourceReferenceEmbeddable.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/process/SourceReferenceEmbeddable.kt
@@ -9,21 +9,21 @@ import javax.persistence.Embeddable
  */
 @Embeddable
 class SourceReferenceEmbeddable(
-  @Column(name = "SOURCE_INSTANCE_ID", nullable = false)
+  @Column(name = "SOURCE_INSTANCE_ID", length = 64, nullable = false)
   var instanceId: String,
-  @Column(name = "SOURCE_EXECUTION_ID", nullable = false)
+  @Column(name = "SOURCE_EXECUTION_ID", length = 64, nullable = false)
   var executionId: String,
-  @Column(name = "SOURCE_DEF_ID", nullable = false)
+  @Column(name = "SOURCE_DEF_ID", length = 64, nullable = false)
   var definitionId: String,
-  @Column(name = "SOURCE_DEF_KEY", nullable = false)
+  @Column(name = "SOURCE_DEF_KEY", length = 64, nullable = false)
   var definitionKey: String,
-  @Column(name = "SOURCE_NAME", nullable = false)
+  @Column(name = "SOURCE_NAME", length = 255, nullable = false)
   var name: String,
-  @Column(name = "APPLICATION_NAME", nullable = false)
+  @Column(name = "APPLICATION_NAME", length = 64, nullable = false)
   var applicationName: String,
-  @Column(name = "SOURCE_TENANT_ID", nullable = true)
+  @Column(name = "SOURCE_TENANT_ID", length = 64, nullable = true)
   var tenantId: String? = null,
-  @Column(name = "SOURCE_TYPE", nullable = false)
+  @Column(name = "SOURCE_TYPE", length = 64, nullable = false)
   var sourceType: String
 ) : Serializable {
 
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntity.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntity.kt
index 4fc26282b..cf62c5f8c 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntity.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntity.kt
@@ -14,12 +14,11 @@ import javax.persistence.*
 @Table(name = "PLF_TASK")
 class TaskEntity(
   @Id
-  @Column(name = "TASK_ID", nullable = false)
+  @Column(name = "TASK_ID", length = 64, nullable = false)
   var taskId: String,
   @Column(name = "TASK_DEF_KEY", nullable = false)
   var taskDefinitionKey: String,
-  @Lob
-  @Column(name = "NAME", nullable = false)
+  @Column(name = "NAME", length = 128, nullable = false)
   var name: String,
   @Column(name = "PRIORITY")
   var priority: Int,
@@ -50,22 +49,21 @@ class TaskEntity(
     ]
   )
   var payloadAttributes: MutableSet<PayloadAttribute> = mutableSetOf(),
-  @Column(name = "BUSINESS_KEY")
+  @Column(name = "BUSINESS_KEY", length = 255, nullable = true)
   var businessKey: String? = null,
-  @Lob
-  @Column(name = "DESCRIPTION")
+  @Column(name = "DESCRIPTION", length = 2048, nullable = true)
   var description: String? = null,
   @Column(name = "FORM_KEY")
   var formKey: String? = null,
   @Column(name = "DATE_CREATED", nullable = false)
   var createdDate: Instant = Instant.now(),
-  @Column(name = "DATE_DUE")
+  @Column(name = "DATE_DUE", nullable = true)
   var dueDate: Instant? = null,
-  @Column(name = "DATE_FOLLOW_UP")
+  @Column(name = "DATE_FOLLOW_UP", nullable = true)
   var followUpDate: Instant? = null,
-  @Column(name = "OWNER_ID")
+  @Column(name = "OWNER_ID", length = 64, nullable = true)
   var owner: String? = null,
-  @Column(name = "ASSIGNEE_ID")
+  @Column(name = "ASSIGNEE_ID", length = 64, nullable = true)
   var assignee: String? = null,
   @Column(name = "PAYLOAD")
   @Lob
diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt
index b9fc9ab6c..2cb069c33 100644
--- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt
+++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt
@@ -1,13 +1,13 @@
 package io.holunda.polyflow.view.jpa.task
 
 import io.holunda.polyflow.view.jpa.auth.AuthorizationPrincipal
-import io.holunda.polyflow.view.jpa.data.DataEntryEntity
 import io.holunda.polyflow.view.jpa.composeOr
 import io.holunda.polyflow.view.jpa.payload.PayloadAttribute
 import io.holunda.polyflow.view.jpa.process.SourceReferenceEmbeddable
 import org.springframework.data.jpa.domain.Specification
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor
 import org.springframework.data.repository.CrudRepository
+import java.time.Instant
 
 /**
  * Repository for accessing tasks.
@@ -27,6 +27,18 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
           builder.isNull(task.get<String>(TaskEntity::assignee.name))
         }
       }
+
+    /**
+     * Is assignee set to specified user.
+     */
+    fun isAssignedTo(assignee: String): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.equal(
+          task.get<String>(TaskEntity::assignee.name),
+          assignee
+        )
+      }
+
     /**
      * Specification for checking authorization of multiple principals.
      */
@@ -50,6 +62,18 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
           application
         )
       }
+
+    /**
+     * Specification for checking the process name.
+     */
+    fun hasProcessName(processName: String): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.equal(
+          task.get<SourceReferenceEmbeddable>(TaskEntity::sourceReference.name).get<String>(SourceReferenceEmbeddable::name.name),
+          processName
+        )
+      }
+
     /**
      * Specification for checking the business key.
      */
@@ -61,14 +85,105 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
         )
       }
 
+    /**
+     * Specification for checking the priority.
+     */
+    fun hasPriority(priority: Int): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.equal(
+          task.get<Int>(TaskEntity::priority.name),
+          priority
+        )
+      }
+
+    /**
+     * Specification for checking the due date.
+     */
+    fun hasDueDate(dueDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.equal(
+          task.get<Instant>(TaskEntity::dueDate.name),
+          dueDate
+        )
+      }
+
+    /**
+     * Specification for checking the due date.
+     */
+    fun hasDueDateBefore(dueDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.or(
+          builder.isNull(task.get<Instant>(TaskEntity::followUpDate.name)),
+          builder.lessThan(
+            task.get(TaskEntity::dueDate.name),
+            dueDate
+          )
+        )
+      }
+
+    /**
+     * Specification for checking the due date.
+     */
+    fun hasDueDateAfter(dueDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.or(
+          builder.isNull(task.get<Instant>(TaskEntity::followUpDate.name)),
+          builder.greaterThan(
+            task.get(TaskEntity::dueDate.name),
+            dueDate
+          )
+        )
+      }
+
+    /**
+     * Specification for checking the follow-up date.
+     */
+    fun hasFollowUpDate(followUpDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.equal(
+          task.get<Instant>(TaskEntity::followUpDate.name),
+          followUpDate
+        )
+      }
+
+    /**
+     * Specification for checking the follow-up date.
+     */
+    fun hasFollowUpDateBefore(followUpDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.or(
+          builder.isNull(task.get<Instant>(TaskEntity::followUpDate.name)),
+          builder.lessThan(
+            task.get(TaskEntity::followUpDate.name),
+            followUpDate
+          )
+        )
+      }
+
+    /**
+     * Specification for checking the follow-up date.
+     */
+    fun hasFollowUpDateAfter(followUpDate: Instant): Specification<TaskEntity> =
+      Specification { task, _, builder ->
+        builder.or(
+          builder.isNull(task.get<Instant>(TaskEntity::followUpDate.name)),
+          builder.greaterThan(
+            task.get(TaskEntity::followUpDate.name),
+            followUpDate
+          )
+        )
+      }
+
     /**
      * Specification for checking the name likeness.
      */
     fun likeName(pattern: String): Specification<TaskEntity> =
       Specification { task, _, builder ->
         builder.like(
-          task.get<String>(TaskEntity::name.name),
-          pattern
+          builder.lower(
+            task.get(TaskEntity::name.name)
+          ),
+          "%${pattern.lowercase()}%"
         )
       }
 
@@ -78,8 +193,10 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
     fun likeDescription(pattern: String): Specification<TaskEntity> =
       Specification { task, _, builder ->
         builder.like(
-          task.get<String>(TaskEntity::description.name),
-          pattern
+          builder.lower(
+            task.get(TaskEntity::description.name)
+          ),
+          "%${pattern.lowercase()}%"
         )
       }
 
@@ -89,27 +206,55 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
     fun likeBusinessKey(pattern: String): Specification<TaskEntity> =
       Specification { task, _, builder ->
         builder.like(
-          task.get<String>(TaskEntity::description.name),
-          pattern
+          builder.lower(
+            task.get(TaskEntity::businessKey.name)
+          ),
+          "%${pattern.lowercase()}%"
         )
       }
 
-
     /**
-     * Specification for checking the payload attribute.
+     * Specification for checking the process name likeness.
      */
-    fun hasTaskPayloadAttribute(name: String, value: String): Specification<TaskEntity> =
+    fun likeProcessName(pattern: String): Specification<TaskEntity> =
       Specification { task, _, builder ->
-        val join = task.join<DataEntryEntity, Set<PayloadAttribute>>(TaskEntity::payloadAttributes.name)
+        builder.like(
+          builder.lower(
+            task.get<SourceReferenceEmbeddable>(TaskEntity::sourceReference.name).get(SourceReferenceEmbeddable::name.name)
+          ),
+          "%${pattern.lowercase()}%"
+        )
+      }
+
+    /**
+     * Specification for checking likeness of multiple task attributes combined by an OR operator.
+     */
+    fun likeTextSearch(pattern: String): Specification<TaskEntity> =
+      likeName(pattern)
+        .or(likeDescription(pattern))
+        .or(likeProcessName(pattern))
+
+    /**
+     * Specification for checking the payload attribute of a task. If multiple values are given, one of them must match.
+     * payload.name = ? AND (payload.value = ? OR payload.value = ? OR ...)
+     */
+    fun hasTaskPayloadAttribute(name: String, values: List<String>): Specification<TaskEntity> =
+      Specification { task, query, builder ->
+        query.distinct(true)
+        val join = task.join<TaskEntity, Set<PayloadAttribute>>(TaskEntity::payloadAttributes.name)
         val pathEquals = builder.equal(
           join.get<String>(PayloadAttribute::path.name),
           name
         )
-        val valueEquals = builder.equal(
-          join.get<String>(PayloadAttribute::value.name),
-          value
-        )
-        builder.and(pathEquals, valueEquals)
+
+        val valueAnyOf = values.map {
+          builder.equal(
+            join.get<String>(PayloadAttribute::value.name),
+            it
+          )
+        }.let { builder.or(*it.toTypedArray()) }
+
+        builder.and(pathEquals, valueAnyOf)
       }
   }
 }
diff --git a/view/jpa/src/sql/persistence.xml b/view/jpa/src/sql/persistence.xml
index 309ea21be..08eb446de 100644
--- a/view/jpa/src/sql/persistence.xml
+++ b/view/jpa/src/sql/persistence.xml
@@ -1,8 +1,8 @@
 <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
   <persistence-unit name="default">
 
+    <!-- Uncomment this to generate the SQL for Axon Framework entities -->
     <!--
-    Uncomment this to generate the SQL for Axon Framework entities
     <class>org.axonframework.eventsourcing.eventstore.jpa.DomainEventEntry</class>
     <class>org.axonframework.eventsourcing.eventstore.jpa.SnapshotEventEntry</class>
     <class>org.axonframework.modelling.saga.repository.jpa.SagaEntry</class>
diff --git a/view/mongo/pom.xml b/view/mongo/pom.xml
index ede0500f8..471588d36 100755
--- a/view/mongo/pom.xml
+++ b/view/mongo/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-view-mongo</artifactId>
diff --git a/view/pom.xml b/view/pom.xml
index 7768c55cf..f00fef936 100644
--- a/view/pom.xml
+++ b/view/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
     <relativePath>../bom/parent/pom.xml</relativePath>
   </parent>
 
diff --git a/view/simple/pom.xml b/view/simple/pom.xml
index 27831f658..438d5c059 100755
--- a/view/simple/pom.xml
+++ b/view/simple/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-view-simple</artifactId>
diff --git a/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt b/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt
index 380ebb854..8767b39ae 100755
--- a/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt
+++ b/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt
@@ -106,7 +106,7 @@ class SimpleTaskPoolService(
 
     val predicates = createTaskPredicates(toCriteria(query.filters))
 
-    val filtered = tasks.values.filter { TasksForUserQuery(query.user).applyFilter(it) }
+    val filtered = tasks.values.filter { TasksForUserQuery(user = query.user, assignedToMeOnly = query.assignedToMeOnly ).applyFilter(it) }
       .asSequence()
       .map { task -> TaskWithDataEntries.correlate(task, dataEntries) }
       .filter { filterByPredicate(it, predicates) }
@@ -185,6 +185,15 @@ class SimpleTaskPoolService(
     return queryForTasks(query)
   }
 
+  /**
+   * Retrieves a list of all user tasks for current user's groups and current user being member of candidate users.
+   */
+  @QueryHandler
+  override fun query(query: TasksForCandidateUserAndGroupQuery): TaskQueryResult {
+    return queryForTasks(query)
+  }
+
+
   /**
    * Retrieves a list of all user tasks.
    */
@@ -361,6 +370,7 @@ class SimpleTaskPoolService(
   private fun updateFilteredQueryQuery(taskId: String) {
     queryUpdateEmitter.updateMapFilterQuery(tasks, taskId, TasksForUserQuery::class.java)
     queryUpdateEmitter.updateMapFilterQuery(tasks, taskId, TasksForGroupQuery::class.java)
+    queryUpdateEmitter.updateMapFilterQuery(tasks, taskId, TasksForCandidateUserAndGroupQuery::class.java)
     queryUpdateEmitter.updateMapFilterQuery(tasks, taskId, AllTasksQuery::class.java)
 
     tasks[taskId]
diff --git a/view/view-api-client/pom.xml b/view/view-api-client/pom.xml
index ecb355c34..484800ad7 100755
--- a/view/view-api-client/pom.xml
+++ b/view/view-api-client/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-view-api-client</artifactId>
diff --git a/view/view-api-client/src/main/kotlin/DataEntryQueryClient.kt b/view/view-api-client/src/main/kotlin/DataEntryQueryClient.kt
index 629ba9607..93ba397ec 100644
--- a/view/view-api-client/src/main/kotlin/DataEntryQueryClient.kt
+++ b/view/view-api-client/src/main/kotlin/DataEntryQueryClient.kt
@@ -11,7 +11,7 @@ import java.util.concurrent.CompletableFuture
 /**
  * Client encapsulating the correct query types (including response types)
  */
-class DataEntryQueryClient(
+open class DataEntryQueryClient(
   private val queryGateway: QueryGateway
 ) {
 
diff --git a/view/view-api-client/src/main/kotlin/ProcessDefinitionQueryClient.kt b/view/view-api-client/src/main/kotlin/ProcessDefinitionQueryClient.kt
index e2786b8ad..e0ae927e6 100644
--- a/view/view-api-client/src/main/kotlin/ProcessDefinitionQueryClient.kt
+++ b/view/view-api-client/src/main/kotlin/ProcessDefinitionQueryClient.kt
@@ -9,7 +9,7 @@ import java.util.concurrent.CompletableFuture
 /**
  * Client encapsulating the correct query types (including response types)
  */
-class ProcessDefinitionQueryClient(
+open class ProcessDefinitionQueryClient(
   private val queryGateway: QueryGateway
 ) {
 
diff --git a/view/view-api-client/src/main/kotlin/ProcessInstanceQueryClient.kt b/view/view-api-client/src/main/kotlin/ProcessInstanceQueryClient.kt
index b050aff15..315c50b4a 100644
--- a/view/view-api-client/src/main/kotlin/ProcessInstanceQueryClient.kt
+++ b/view/view-api-client/src/main/kotlin/ProcessInstanceQueryClient.kt
@@ -13,7 +13,7 @@ import java.util.concurrent.CompletableFuture
 /**
  * Client encapsulating the correct query types (including response types)
  */
-class ProcessInstanceQueryClient(
+open class ProcessInstanceQueryClient(
   private val queryGateway: QueryGateway
 ) {
 
diff --git a/view/view-api-client/src/main/kotlin/ProcessVariableQueryClient.kt b/view/view-api-client/src/main/kotlin/ProcessVariableQueryClient.kt
index c6b74497f..ae0bab539 100644
--- a/view/view-api-client/src/main/kotlin/ProcessVariableQueryClient.kt
+++ b/view/view-api-client/src/main/kotlin/ProcessVariableQueryClient.kt
@@ -15,7 +15,7 @@ import java.util.concurrent.CompletableFuture
 /**
  * Client encapsulating the correct query types (including response types)
  */
-class ProcessVariableQueryClient(
+open class ProcessVariableQueryClient(
   private val queryGateway: QueryGateway
 ) {
 
diff --git a/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt b/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt
index 4b9da2e1b..05c3e687f 100644
--- a/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt
+++ b/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt
@@ -128,4 +128,10 @@ object QueryGatewayExt {
    */
   fun QueryGateway.tasksWithDataEntriesForGroup(query: TasksWithDataEntriesForGroupQuery): CompletableFuture<TasksWithDataEntriesQueryResult> =
     TaskQueryClient(this).query(query)
+
+  /**
+   * @see [TasksForGroupQuery]
+   */
+  fun QueryGateway.tasksForCandidateUserAndGroup(query: TasksForCandidateUserAndGroupQuery): CompletableFuture<TaskQueryResult> = TaskQueryClient(this).query(query)
+
 }
diff --git a/view/view-api-client/src/main/kotlin/TaskQueryClient.kt b/view/view-api-client/src/main/kotlin/TaskQueryClient.kt
index 2f8e19129..8cd29dc4f 100644
--- a/view/view-api-client/src/main/kotlin/TaskQueryClient.kt
+++ b/view/view-api-client/src/main/kotlin/TaskQueryClient.kt
@@ -9,7 +9,7 @@ import java.util.concurrent.CompletableFuture
 /**
  * Client encapsulating the correct query types (including response types)
  */
-class TaskQueryClient(
+open class TaskQueryClient(
   private val queryGateway: QueryGateway
 ) {
 
@@ -17,7 +17,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksForUserQuery
    */
-  fun query(query: TasksForUserQuery): CompletableFuture<TaskQueryResult> =
+  open fun query(query: TasksForUserQuery): CompletableFuture<TaskQueryResult> =
     queryGateway.query(
       query,
       ResponseTypes.instanceOf(TaskQueryResult::class.java)
@@ -27,7 +27,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TaskForIdQuery
    */
-  fun query(query: TaskForIdQuery): CompletableFuture<Optional<Task>> = queryGateway.query(
+  open fun query(query: TaskForIdQuery): CompletableFuture<Optional<Task>> = queryGateway.query(
     query,
     ResponseTypes.optionalInstanceOf(Task::class.java)
   )
@@ -36,7 +36,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksForApplicationQuery
    */
-  fun query(query: TasksForApplicationQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
+  open fun query(query: TasksForApplicationQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TaskQueryResult::class.java)
   )
@@ -45,7 +45,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TaskWithDataEntriesForIdQuery
    */
-  fun query(query: TaskWithDataEntriesForIdQuery): CompletableFuture<Optional<TaskWithDataEntries>> = queryGateway.query(
+  open fun query(query: TaskWithDataEntriesForIdQuery): CompletableFuture<Optional<TaskWithDataEntries>> = queryGateway.query(
     query,
     ResponseTypes.optionalInstanceOf(TaskWithDataEntries::class.java)
   )
@@ -54,7 +54,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksWithDataEntriesForUserQuery
    */
-  fun query(query: TasksWithDataEntriesForUserQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
+  open fun query(query: TasksWithDataEntriesForUserQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TasksWithDataEntriesQueryResult::class.java)
   )
@@ -63,7 +63,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TaskCountByApplicationQuery
    */
-  fun query(query: TaskCountByApplicationQuery): CompletableFuture<List<ApplicationWithTaskCount>> = queryGateway.query(
+  open fun query(query: TaskCountByApplicationQuery): CompletableFuture<List<ApplicationWithTaskCount>> = queryGateway.query(
     query,
     ResponseTypes.multipleInstancesOf(ApplicationWithTaskCount::class.java)
   )
@@ -72,7 +72,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.AllTasksQuery
    */
-  fun query(query: AllTasksQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
+  open fun query(query: AllTasksQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TaskQueryResult::class.java)
   )
@@ -81,7 +81,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksWithDataEntriesQueryResult
    */
-  fun query(query: AllTasksWithDataEntriesQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
+  open fun query(query: AllTasksWithDataEntriesQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TasksWithDataEntriesQueryResult::class.java)
   )
@@ -90,7 +90,7 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksForGroupQuery
    */
-  fun query(query: TasksForGroupQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
+  open fun query(query: TasksForGroupQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TaskQueryResult::class.java)
   )
@@ -99,8 +99,18 @@ class TaskQueryClient(
    * @see io.holunda.polyflow.view.query.task.TaskApi.query
    * @see io.holunda.polyflow.view.query.task.TasksWithDataEntriesForGroupQuery
    */
-  fun query(query: TasksWithDataEntriesForGroupQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
+  open fun query(query: TasksWithDataEntriesForGroupQuery): CompletableFuture<TasksWithDataEntriesQueryResult> = queryGateway.query(
     query,
     ResponseTypes.instanceOf(TasksWithDataEntriesQueryResult::class.java)
   )
+
+  /**
+   * @see io.holunda.polyflow.view.query.task.TaskApi.query
+   * @see io.holunda.polyflow.view.query.task.TasksForCandidateUserAndGroupQuery
+   */
+  open fun query(query: TasksForCandidateUserAndGroupQuery): CompletableFuture<TaskQueryResult> = queryGateway.query(
+    query,
+    ResponseTypes.instanceOf(TaskQueryResult::class.java)
+  )
+
 }
diff --git a/view/view-api/pom.xml b/view/view-api/pom.xml
index a7e04857a..bb94dfa00 100755
--- a/view/view-api/pom.xml
+++ b/view/view-api/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.holunda.polyflow</groupId>
     <artifactId>polyflow-view-parent</artifactId>
-    <version>3.8.3-SNAPSHOT</version>
+    <version>3.14.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>polyflow-view-api</artifactId>
diff --git a/view/view-api/src/main/kotlin/filter/Filter.kt b/view/view-api/src/main/kotlin/filter/Filter.kt
index 1f0cdcf1a..2d2cebe9a 100755
--- a/view/view-api/src/main/kotlin/filter/Filter.kt
+++ b/view/view-api/src/main/kotlin/filter/Filter.kt
@@ -61,7 +61,7 @@ internal fun compareOperator(sign: String): CompareOperator =
       }
     }
 
-    EQUALS-> { filter, actual -> filter.toString() == actual.toString() }
+    EQUALS -> { filter, actual -> filter.toString() == actual.toString() }
 
     else -> throw IllegalArgumentException("Unsupported operator $sign")
   }
@@ -105,7 +105,8 @@ fun filterByPredicate(value: TaskWithDataEntries, wrapper: TaskPredicateWrapper)
     // constraint is defined on task and matches on task property
     || (wrapper.taskAttributePredicate != null && wrapper.taskAttributePredicate.test(value.task) && wrapper.taskPayloadPredicate == null)
     // constraint is defined on data and matches on data entry property
-    || (wrapper.taskAttributePredicate == null && (wrapper.taskPayloadPredicate != null && (value.dataEntries.asSequence().map { dataEntry -> dataEntry.payload }
+    || (wrapper.taskAttributePredicate == null && (wrapper.taskPayloadPredicate != null && (value.dataEntries.asSequence()
+    .map { dataEntry -> dataEntry.payload }
     .find { payload -> wrapper.taskPayloadPredicate.test(payload) } != null || wrapper.taskPayloadPredicate.test(value.task.payload))))
     // both constraints
     || (wrapper.taskAttributePredicate != null && wrapper.taskAttributePredicate.test(value.task) && (wrapper.taskPayloadPredicate != null && (value.dataEntries.asSequence()
@@ -140,7 +141,9 @@ fun filterByPredicate(value: Task, wrapper: TaskPredicateWrapper): Boolean =
     // constraint is defined on data and matches
     || (wrapper.taskAttributePredicate == null && wrapper.taskPayloadPredicate != null && wrapper.taskPayloadPredicate.test(value.payload))
     // both constraints
-    || (wrapper.taskAttributePredicate != null && wrapper.taskAttributePredicate.test(value) && wrapper.taskPayloadPredicate != null && wrapper.taskPayloadPredicate.test(value.payload))
+    || (wrapper.taskAttributePredicate != null && wrapper.taskAttributePredicate.test(value) && wrapper.taskPayloadPredicate != null && wrapper.taskPayloadPredicate.test(
+    value.payload
+  ))
 
 
 /**
@@ -245,7 +248,7 @@ internal fun toCriterion(filter: String): Criterion {
   }.map { it.trim() }
 
   // special handling for simple infix operators of form <field><op><value>
-  require(segments.size == 3 && segments[0].isNotBlank() && segments[0].isNotBlank()) { "Failed to create criteria from $filter." }
+  require(segments.size == 3 && segments[0].isNotBlank() && segments[1].isNotBlank()) { "Failed to create criteria from $filter." }
 
   return when {
     isTaskAttribute(segments[0]) -> {
@@ -268,7 +271,14 @@ internal fun toCriterion(filter: String): Criterion {
 internal fun isTaskAttribute(propertyName: String): Boolean =
   propertyName.startsWith(TASK_PREFIX)
     && propertyName.length > TASK_PREFIX.length
-    && Task::class.memberProperties.map { it.name }.contains(propertyName.substring(TASK_PREFIX.length))
+    && propertyName.substring(TASK_PREFIX.length).let { taskAttribute ->
+    when {
+      Task::class.memberProperties.map { it.name }.contains(taskAttribute) -> true
+      taskAttribute == "processName" -> true
+      taskAttribute == "textSearch" -> true
+      else -> false
+    }
+  }
 
 /**
  * Checks is a property is a data entry attribute.
@@ -276,10 +286,13 @@ internal fun isTaskAttribute(propertyName: String): Boolean =
 internal fun isDataEntryAttribute(propertyName: String): Boolean =
   propertyName.startsWith(DATA_PREFIX)
     && propertyName.length > DATA_PREFIX.length
-    && (
-    DataEntry::class.memberProperties.map { it.name }.contains(propertyName.substring(DATA_PREFIX.length)) ||
-      DataEntryState::class.memberProperties.map { "${DataEntry::state.name}.${it.name}" }.contains(propertyName.substring(DATA_PREFIX.length))
-    )
+    && propertyName.substring(DATA_PREFIX.length).let { dataEntryAttribute ->
+    when {
+      DataEntry::class.memberProperties.map { it.name }.contains(dataEntryAttribute) -> true
+      DataEntryState::class.memberProperties.map { "${DataEntry::state.name}.${it.name}" }.contains(dataEntryAttribute) -> true
+      else -> false
+    }
+  }
 
 /**
  * Criterion.
diff --git a/view/view-api/src/main/kotlin/query/PageableSortableQuery.kt b/view/view-api/src/main/kotlin/query/PageableSortableQuery.kt
index f9f9dc340..13dc620b2 100644
--- a/view/view-api/src/main/kotlin/query/PageableSortableQuery.kt
+++ b/view/view-api/src/main/kotlin/query/PageableSortableQuery.kt
@@ -1,5 +1,10 @@
 package io.holunda.polyflow.view.query
 
+import io.holunda.polyflow.view.sort.SortDirection.ASCENDING
+import io.holunda.polyflow.view.sort.SortDirection.DESCENDING
+import kotlin.reflect.KClass
+import kotlin.reflect.full.declaredMemberProperties
+
 /**
  * Query interface for queries specifying the total amount of results, page and sort.
  */
@@ -7,4 +12,33 @@ interface PageableSortableQuery {
   val page: Int
   val size: Int
   val sort: String?
+
+  /**
+   * Checks that the sort parameter is correctly specified.
+   * @param clazz class containing member properties to be used as allowed fields.
+   */
+  fun <C : KClass<*>> sanitizeSort(clazz: C) {
+    sanitizeSort(clazz.declaredMemberProperties.map { it.name }.toSet())
+  }
+
+  /**
+   * Checks that the sort parameter is correctly specified.
+   */
+  fun sanitizeSort(fieldNames: Set<String>) {
+    if (sort == null) {
+      return
+    }
+    val direction = sort!!.substring(0, 1)
+    require(
+      direction == ASCENDING.sign
+        || direction == DESCENDING.sign
+    ) { "Sort must start either with '${ASCENDING.sign}' or '${DESCENDING.sign}' but it was starting with '$direction'" }
+    val parameter = sort!!.substring(1)
+    require(fieldNames.contains(parameter)) {
+      "Sort parameter must be one of ${
+        fieldNames.joinToString(", ")
+      } but it was $parameter."
+    }
+  }
+
 }
diff --git a/view/view-api/src/main/kotlin/query/QueryResult.kt b/view/view-api/src/main/kotlin/query/QueryResult.kt
index e8103dd31..d1d98a94c 100644
--- a/view/view-api/src/main/kotlin/query/QueryResult.kt
+++ b/view/view-api/src/main/kotlin/query/QueryResult.kt
@@ -2,6 +2,8 @@ package io.holunda.polyflow.view.query
 
 /**
  * Query result representing a list of elements and the total number of elements.
+ * @param [T] tpe of resulting element.
+ * @param [S] type of a concrete query (to be returned on query operations like slice).
  * @param elements elements in the query result.
  * @param totalElementCount in the result before the slice has been applied.
  */
@@ -13,7 +15,7 @@ open class QueryResult<T : Any, S : QueryResult<T, S>>(
 
   /**
    * Slices the result.
-   * @param query contaning slicing information.
+   * @param query containing slicing information.
    * @return result having the specified number of elements.
    */
   open fun slice(query: PageableSortableQuery): QueryResult<T, S> {
diff --git a/view/view-api/src/main/kotlin/query/task/TaskApi.kt b/view/view-api/src/main/kotlin/query/task/TaskApi.kt
index c880d9f77..3e6c88e64 100644
--- a/view/view-api/src/main/kotlin/query/task/TaskApi.kt
+++ b/view/view-api/src/main/kotlin/query/task/TaskApi.kt
@@ -29,7 +29,6 @@ interface TaskApi {
    */
   fun query(query: AllTasksWithDataEntriesQuery): TasksWithDataEntriesQueryResult
 
-
   /**
    * Count tasks and group by application.
    */
@@ -45,6 +44,11 @@ interface TaskApi {
    */
   fun query(query: TasksForGroupQuery): TaskQueryResult
 
+  /**
+   * Retrieves tasks for given user's groups and user's being candidates.
+   */
+  fun query(query: TasksForCandidateUserAndGroupQuery): TaskQueryResult
+
   /**
    * Retrieve task by given id.
    */
diff --git a/view/view-api/src/main/kotlin/query/task/TasksForCandidateUserAndGroupQuery.kt b/view/view-api/src/main/kotlin/query/task/TasksForCandidateUserAndGroupQuery.kt
new file mode 100755
index 000000000..f8983cc9d
--- /dev/null
+++ b/view/view-api/src/main/kotlin/query/task/TasksForCandidateUserAndGroupQuery.kt
@@ -0,0 +1,29 @@
+package io.holunda.polyflow.view.query.task
+
+import io.holunda.polyflow.view.Task
+import io.holunda.polyflow.view.auth.User
+
+/**
+ * Query for tasks visible for user.
+ * @param user - the user with groups accessing the tasks.
+ * @param includeAssigned flag indicating if assigned tasks are returned.
+ * @param page - page to read, zero-based index.
+ * @param size - size of the page
+ * @param sort - optional attribute to sort by.
+ * @param filters - the filters to further filter down the tasks.
+ */
+data class TasksForCandidateUserAndGroupQuery(
+  val user: User,
+  val includeAssigned: Boolean = true,
+  override val page: Int = 0,
+  override val size: Int = Int.MAX_VALUE,
+  override val sort: String? = null,
+  override val filters: List<String> = listOf()
+) : PageableSortableFilteredTaskQuery {
+
+  override fun applyFilter(element: Task): Boolean =
+    (element.candidateGroups.any { candidateGroup -> this.user.groups.contains(candidateGroup) }
+      || element.candidateUsers.contains(this.user.username)) && ((element.assignee != null) == includeAssigned)
+
+}
+
diff --git a/view/view-api/src/main/kotlin/query/task/TasksForUserQuery.kt b/view/view-api/src/main/kotlin/query/task/TasksForUserQuery.kt
index d05524524..5180445f7 100755
--- a/view/view-api/src/main/kotlin/query/task/TasksForUserQuery.kt
+++ b/view/view-api/src/main/kotlin/query/task/TasksForUserQuery.kt
@@ -2,12 +2,11 @@ package io.holunda.polyflow.view.query.task
 
 import io.holunda.polyflow.view.Task
 import io.holunda.polyflow.view.auth.User
-import io.holunda.polyflow.view.query.FilterQuery
-import io.holunda.polyflow.view.query.PageableSortableQuery
 
 /**
  * Query for tasks visible for user.
  * @param user - the user with groups accessing the tasks.
+ * @param assignedToMeOnly flag indicating if the resulting tasks must be assigned to the user only.
  * @param page - page to read, zero-based index.
  * @param size - size of the page
  * @param sort - optional attribute to sort by.
@@ -15,18 +14,37 @@ import io.holunda.polyflow.view.query.PageableSortableQuery
  */
 data class TasksForUserQuery(
   val user: User,
+  val assignedToMeOnly: Boolean = false,
   override val page: Int = 0,
   override val size: Int = Int.MAX_VALUE,
   override val sort: String? = null,
   override val filters: List<String> = listOf()
 ) : PageableSortableFilteredTaskQuery {
 
+  /**
+   * Compatibility constructor for old clients.
+   */
+  @Deprecated(message = "Please use other constructor setting the assignedToMeOnly.")
+  constructor(user: User, page: Int = 0, size: Int = Int.MAX_VALUE, sort: String? = null, filters: List<String> = listOf()): this(
+    user = user,
+    assignedToMeOnly = false,
+    page = page,
+    size = size,
+    sort = sort,
+    filters = filters
+  )
+
   override fun applyFilter(element: Task): Boolean =
-  // assignee
-    element.assignee == this.user.username
+    if (assignedToMeOnly) {
+      // assignee
+      element.assignee == this.user.username
+    } else {
       // candidate user
-      || (element.candidateUsers.contains(this.user.username))
-      // candidate groups
-      || (element.candidateGroups.any { candidateGroup -> this.user.groups.contains(candidateGroup) })
+      element.candidateUsers.contains(this.user.username)
+        // candidate groups
+        || element.candidateGroups.any { candidateGroup -> this.user.groups.contains(candidateGroup) }
+        // assignee
+        || element.assignee == this.user.username
+    }
 }
 
diff --git a/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForGroupQuery.kt b/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForGroupQuery.kt
index 81ea69e8f..e183a3f7e 100755
--- a/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForGroupQuery.kt
+++ b/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForGroupQuery.kt
@@ -1,6 +1,5 @@
 package io.holunda.polyflow.view.query.task
 
-import io.holunda.polyflow.view.Task
 import io.holunda.polyflow.view.TaskWithDataEntries
 import io.holunda.polyflow.view.auth.User
 import io.holunda.polyflow.view.query.FilterQuery
diff --git a/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForUserQuery.kt b/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForUserQuery.kt
index 191664431..33b975d36 100755
--- a/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForUserQuery.kt
+++ b/view/view-api/src/main/kotlin/query/task/TasksWithDataEntriesForUserQuery.kt
@@ -8,6 +8,7 @@ import io.holunda.polyflow.view.query.PageableSortableQuery
 /**
  * Query for tasks with correlated data entries for given user.
  * @param user user able to see the tasks.
+ * @param assignedToMeOnly flag indicating if the resulting tasks must be assigned to the user only.
  * @param page current page, zero-based index.
  * @param size number of entries on every page.
  * @param sort property name of the {@link TaskWithDataEntries} to sort.
@@ -15,12 +16,28 @@ import io.holunda.polyflow.view.query.PageableSortableQuery
  */
 data class TasksWithDataEntriesForUserQuery(
   val user: User,
+  val assignedToMeOnly: Boolean = false,
   override val page: Int = 0,
   override val size: Int = Int.MAX_VALUE,
   override val sort: String? = null,
   val filters: List<String> = listOf()
 ) : FilterQuery<TaskWithDataEntries>, PageableSortableQuery {
 
+  /**
+   * Compatibility constructor for old clients.
+   */
+  @Deprecated("Please use other constructor setting the assignedToMeOnly.")
+  constructor(user: User, page: Int = 0, size: Int = Int.MAX_VALUE, sort: String? = null, filters: List<String> = listOf()) : this(
+    user = user, assignedToMeOnly = false, page = page, size = size, sort = sort, filters = filters
+  )
+
   override fun applyFilter(element: TaskWithDataEntries): Boolean =
-    TasksForUserQuery(user, page, size, sort, filters).applyFilter(element.task)
+    TasksForUserQuery(
+      user = user,
+      assignedToMeOnly = assignedToMeOnly,
+      page = page,
+      size = size,
+      sort = sort,
+      filters = filters
+    ).applyFilter(element.task)
 }


Test patch:
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/collector/properties/CamundaTaskpoolCollectorPropertiesExtendedTest.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/collector/properties/CamundaTaskpoolCollectorPropertiesExtendedTest.kt
index 3ebe6ac30..d0f1ba439 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/collector/properties/CamundaTaskpoolCollectorPropertiesExtendedTest.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/collector/properties/CamundaTaskpoolCollectorPropertiesExtendedTest.kt
@@ -4,6 +4,7 @@ import com.thoughtworks.xstream.XStream
 import io.holunda.polyflow.taskpool.collector.CamundaTaskpoolCollectorConfiguration
 import io.holunda.polyflow.taskpool.collector.CamundaTaskpoolCollectorProperties
 import io.holunda.polyflow.taskpool.collector.TaskCollectorEnricherType
+import io.holunda.polyflow.taskpool.collector.task.TaskVariableLoader
 import io.holunda.polyflow.taskpool.collector.task.VariablesEnricher
 import io.holunda.polyflow.taskpool.sender.process.definition.ProcessDefinitionCommandSender
 import io.holunda.polyflow.taskpool.sender.process.instance.ProcessInstanceCommandSender
@@ -15,6 +16,9 @@ import org.axonframework.eventhandling.EventBus
 import org.axonframework.serialization.Serializer
 import org.axonframework.serialization.xml.XStreamSerializer
 import org.camunda.bpm.engine.RepositoryService
+import org.camunda.bpm.engine.RuntimeService
+import org.camunda.bpm.engine.TaskService
+import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
 import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties
 import org.junit.jupiter.api.Test
 import org.mockito.Mockito.mock
@@ -79,12 +83,38 @@ internal class CamundaTaskpoolCollectorPropertiesExtendedTest {
       }
   }
 
+  @Test
+  fun `loads properties configuration to ignore listeners`() {
+    contextRunner
+      .withUserConfiguration(TestMockConfiguration::class.java)
+      .withUserConfiguration(AdditionalMockConfiguration::class.java)
+      .withPropertyValues(
+        "spring.application.name=my-test-application",
+        "camunda.bpm.eventing.task=false",
+        "polyflow.integration.collector.camunda.task.enabled=true",
+        "polyflow.integration.collector.camunda.task.enricher.type=custom",
+        "polyflow.integration.collector.camunda.task.excluded-task-event-names=assignment",
+        "polyflow.integration.collector.camunda.task.excluded-history-event-names=add-identity-link,delete-identity-link",
+      ).run {
+
+        assertThat(it.getBean(CamundaTaskpoolCollectorProperties::class.java)).isNotNull
+        val props: CamundaTaskpoolCollectorProperties = it.getBean(CamundaTaskpoolCollectorProperties::class.java)
+
+        assertThat(props.task.enabled).isTrue
+        assertThat(props.task.collectTaskEvent("assignment")).isFalse()
+        assertThat(props.task.collectHistoryEvent("add-identity-link")).isFalse()
+        assertThat(props.task.collectHistoryEvent("delete-identity-link")).isFalse()
+      }
+  }
+
+
   /**
    * Config class without configuration annotation not to confuse others.
    */
   private class AdditionalMockConfiguration {
     @Bean
     fun variablesEnricher(): VariablesEnricher = mock(VariablesEnricher::class.java)
+
   }
 
   /**
@@ -118,5 +148,14 @@ internal class CamundaTaskpoolCollectorPropertiesExtendedTest {
 
     @Bean
     fun repositoryService(): RepositoryService = mock()
+
+    @Bean
+    fun runtimeService(): RuntimeService = mock()
+
+    @Bean
+    fun taskService(): TaskService = mock()
+
+    @Bean
+    fun commandExecutor(): CommandExecutor = mock()
   }
 }
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/TestDriver.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/TestDriver.kt
index e6f6e4870..7b35e6ff1 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/TestDriver.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/TestDriver.kt
@@ -145,8 +145,8 @@ class TestDriver(
 
   }
 
-  fun assertProcessInstanceWaitsInUserTask(instance: ProcessInstance) {
-    BpmnAwareTests.assertThat(instance).isWaitingAt(TASK_DEFINITION_KEY)
+  fun assertProcessInstanceWaitsInUserTask(instance: ProcessInstance, taskDefinitionKey: String = TASK_DEFINITION_KEY) {
+    BpmnAwareTests.assertThat(instance).isWaitingAt(taskDefinitionKey)
   }
 
   /*
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/ProcessDefinitionServiceITest.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/ProcessDefinitionServiceITest.kt
index 125771b68..f5914dfe8 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/ProcessDefinitionServiceITest.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/ProcessDefinitionServiceITest.kt
@@ -86,6 +86,52 @@ class ProcessDefinitionServiceITest {
     assertThat(definitions[0].candidateStarterGroups).containsExactlyElementsOf(listOf("muppetshow"))
   }
 
+  @Test
+  fun `should not deliver process starter if no start form is available`() {
+
+    val processId = "my-id"
+    val startEventId = "start"
+    val modelInstance = Bpmn
+      .createExecutableProcess(processId)
+      .startEvent(startEventId)
+      .endEvent("end")
+      .done().apply {
+        getModelElementById<ModelElementInstance>(processId).setAttributeValue("name", "My Process")
+        getModelElementById<ModelElementInstance>(processId).setAttributeValueNs(NS_CAMUNDA, "candidateStarterGroups", "muppetshow")
+      }
+
+    repositoryService
+      .createDeployment()
+      .addModelInstance("process-without-start-form.bpmn", modelInstance)
+      .deploy()
+
+
+    val definitions = processDefinitionService.getProcessDefinitions(processEngine.processEngineConfiguration as ProcessEngineConfigurationImpl)
+
+    assertThat(definitions).isNotEmpty
+    assertThat(definitions[0].processName).isEqualTo("My Process")
+    assertThat(definitions[0].processDefinitionKey).isEqualTo("my-id")
+    assertThat(definitions[0].processDefinitionVersion).isEqualTo(1)
+    assertThat(definitions[0].formKey).isNull()
+    assertThat(definitions[0].candidateStarterGroups).containsExactlyElementsOf(listOf("muppetshow"))
+  }
+
+  @Test
+  fun `should not deliver process starter if only two message start events are available`() {
+    repositoryService
+      .createDeployment()
+      .addClasspathResource("itest/message_start_event.bpmn")
+      .deploy()
+
+    val definitions = processDefinitionService.getProcessDefinitions(processEngine.processEngineConfiguration as ProcessEngineConfigurationImpl)
+
+    assertThat(definitions).isNotEmpty
+    assertThat(definitions[0].processName).isEqualTo("My Process")
+    assertThat(definitions[0].processDefinitionKey).isEqualTo("my-id")
+    assertThat(definitions[0].processDefinitionVersion).isEqualTo(1)
+    assertThat(definitions[0].formKey).isNull()
+  }
+
   /**
    * Internal test application.
    */
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
index 92dbec1d0..963bf20f6 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.module.kotlin.convertValue
 import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
 import io.holunda.camunda.taskpool.api.task.*
 import io.holunda.polyflow.taskpool.EnableCamundaTaskpoolCollector
+import io.holunda.polyflow.taskpool.collector.task.TaskEventCollectorService
 import io.holunda.polyflow.taskpool.itest.TestDriver
 import io.holunda.polyflow.taskpool.itest.TestDriver.Companion.DEFAULT_VARIABLES
 import io.holunda.polyflow.taskpool.itest.TestDriver.Companion.createTaskCommand
@@ -17,6 +18,7 @@ import org.axonframework.commandhandling.gateway.CommandGateway
 import org.camunda.bpm.engine.RepositoryService
 import org.camunda.bpm.engine.RuntimeService
 import org.camunda.bpm.engine.TaskService
+import org.camunda.bpm.engine.delegate.DelegateTask
 import org.camunda.bpm.engine.delegate.TaskListener
 import org.camunda.bpm.engine.impl.interceptor.Command
 import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
@@ -32,6 +34,8 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.boot.test.mock.mockito.MockBean
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Primary
+import org.springframework.context.event.EventListener
+import org.springframework.core.annotation.Order
 import org.springframework.test.annotation.DirtiesContext
 import org.springframework.test.context.ActiveProfiles
 import java.time.Instant.now
@@ -647,6 +651,43 @@ internal class TaskCollectorITest {
 
   }
 
+  /**
+   * The process is started and waits in a user task. The user task has a task listener that changes some local process variables on create.
+   * The create command should contain the local variables.
+   */
+  @Test
+  fun `updates variables with create listener implemented as spring event handler`() {
+
+    // deploy
+    driver.deployProcess(
+      createUserTaskProcess(
+        taskDefinitionKey = "eventing",
+        taskListeners = listOf(
+          "create" to "#{setTaskLocalVariables}" // this one will be ignored, since it is invoked after enrichment
+        )
+      )
+    )
+
+
+    // start
+    val instance = driver.startProcessInstance(variables = Variables.createVariables().putValue("overriddenVariable", "global-value"))
+    driver.assertProcessInstanceWaitsInUserTask(instance, "eventing")
+
+    val createCommand = createTaskCommand(
+      variables = Variables.createVariables()
+        .putValue("taskLocalOnlyVariable", "only-value-by-event-handler")
+        .putValue("overriddenVariable", "local-value-by-event-handler")
+    )
+
+
+    waitAtMost(3, TimeUnit.SECONDS).untilAsserted {
+      verify(commandListGateway).sendToGateway(
+        listOf(createCommand)
+      )
+    }
+
+    verifyNoMoreInteractions(commandListGateway)
+  }
 
   /**
    * Internal test application.
@@ -694,6 +735,26 @@ internal class TaskCollectorITest {
       delegateTask.dueDate = TestDriver.NOW
       delegateTask.followUpDate = TestDriver.NOW
     }
+
+    /**
+     * A task listener that sets some local variables.
+     */
+    @Order(TaskEventCollectorService.ORDER - 80)
+    @EventListener(condition = "#delegateTask.taskDefinitionKey.equals('eventing') && #delegateTask.eventName.equals('create')")
+    fun setTaskLocalVariables(delegateTask: DelegateTask) {
+      delegateTask.setVariableLocal("taskLocalOnlyVariable", "only-value-by-event-handler")
+      delegateTask.setVariableLocal("overriddenVariable", "local-value-by-event-handler")
+    }
+
+
+    /**
+     * A task listener that sets some local variables.
+     */
+    @Bean
+    fun setTaskLocalVariables() = TaskListener { delegateTask ->
+      delegateTask.setVariableLocal("taskLocalOnlyVariable", "only-value")
+      delegateTask.setVariableLocal("overriddenVariable", "local-value")
+    }
   }
 
 }
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/txjob/TaskTxJobSenderITest.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/txjob/TaskTxJobSenderITest.kt
index bd5b4d011..eeddff42d 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/txjob/TaskTxJobSenderITest.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/txjob/TaskTxJobSenderITest.kt
@@ -7,17 +7,16 @@ import io.holunda.polyflow.taskpool.itest.TestDriver
 import io.holunda.polyflow.taskpool.itest.TestDriver.Companion.createUserTaskProcess
 import io.holunda.polyflow.taskpool.sender.gateway.CommandListGateway
 import org.assertj.core.api.Assertions
+import org.awaitility.Awaitility
 import org.axonframework.commandhandling.gateway.CommandGateway
 import org.camunda.bpm.engine.ManagementService
 import org.camunda.bpm.engine.RepositoryService
 import org.camunda.bpm.engine.RuntimeService
-import org.camunda.bpm.engine.TaskService
-import org.camunda.bpm.engine.impl.interceptor.Command
-import org.camunda.bpm.engine.impl.interceptor.CommandExecutor
+import org.camunda.bpm.engine.delegate.TaskListener
+import org.camunda.bpm.engine.impl.persistence.entity.MessageEntity
 import org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.*
+import org.camunda.bpm.engine.variable.Variables
 import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication
-import org.junit.After
-import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Disabled
 import org.junit.jupiter.api.Test
@@ -30,20 +29,20 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.boot.test.mock.mockito.MockBean
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Primary
-import org.springframework.test.annotation.Commit
 import org.springframework.test.annotation.DirtiesContext
-import org.springframework.test.annotation.Rollback
 import org.springframework.test.context.ActiveProfiles
-import org.springframework.test.context.transaction.AfterTransaction
+import org.springframework.transaction.annotation.Propagation
 import org.springframework.transaction.annotation.Transactional
-import org.springframework.transaction.support.TransactionSynchronization
+import org.springframework.transaction.support.TransactionTemplate
 import java.util.*
+import java.util.concurrent.TimeUnit
 
 
-@Disabled("Understand how to test this")
 @SpringBootTest(classes = [TaskTxJobSenderITest.TaskTxJobSenderTestApplication::class], webEnvironment = SpringBootTest.WebEnvironment.MOCK)
 @ActiveProfiles("txjob-sender-itest")
 @DirtiesContext
+@Transactional
+@Disabled("This test sometimes fails because it can't create a new TX (Entity Manager is closed).")
 internal class TaskTxJobSenderITest {
 
   @MockBean
@@ -59,63 +58,111 @@ internal class TaskTxJobSenderITest {
   lateinit var managementService: ManagementService
 
   @Autowired
-  lateinit var taskServiceService: TaskService
-
-
-  @Autowired
-  lateinit var commandExecutor: CommandExecutor
+  lateinit var txTemplate: TransactionTemplate
 
+  lateinit var createCommand: CreateTaskCommand
 
   private val driver: TestDriver by lazy {
     TestDriver(repositoryService, runtimeService)
   }
 
-
   @BeforeEach
-  fun `start process and create user task`() {
-
+  fun `setup tx template`() {
+    txTemplate.propagationBehavior = Propagation.REQUIRES_NEW.value()
   }
 
   @Test
-  @Transactional
-  @Commit
   fun `creates task in process`() {
-    // deploy
-    driver.deployProcess(
-      createUserTaskProcess()
-    )
 
-//    commandExecutor.execute {
-//      Command {
-        // start
-        val instance = driver.startProcessInstance()
-        // instance is started
-        assertThat(instance).isStarted
-        // user task
-        driver.assertProcessInstanceWaitsInUserTask(instance)
-//      }
-//    }
+    doInTransaction {
+      // deploy
+      driver.deployProcess(
+        createUserTaskProcess()
+      )
 
+      // start
+      val instance = driver.startProcessInstance()
+      // instance is started
+      assertThat(instance).isStarted
+      // user task
+      driver.assertProcessInstanceWaitsInUserTask(instance)
 
-    /*
+      verifyNoMoreInteractions(commandListGateway)
+
+      createCommand = TestDriver.createTaskCommand()
+    }
+
+    assertAndExecuteCommandSendingJob()
+
+    verify(commandListGateway).sendToGateway(
+      listOf(createCommand)
+    )
+  }
 
-        val createCommand = createTaskCommand(
-          candidateUsers = setOf("piggy"),
-          candidateGroups = setOf("muppetshow"),
+  /**
+   * The process is started and waits in a user task. The user task has a task listener that changes some local process variables on create.
+   * The create command should contain the local variables.
+   */
+  @Test
+  @Disabled("Find out why the local listener update always gt into the next TX and how to deal with it")
+  fun `updates variables with create listener`() {
+
+    doInTransaction {
+      // deploy
+      driver.deployProcess(
+        createUserTaskProcess(
+          taskListeners = listOf(
+            "create" to "#{setTaskLocalVariables}"
+          )
         )
-    */
+      )
+
+      // start
+      val instance = driver.startProcessInstance(variables = Variables.createVariables().putValue("overriddenVariable", "global-value"))
+      driver.assertProcessInstanceWaitsInUserTask(instance)
+
+      verifyNoMoreInteractions(commandListGateway)
 
+      createCommand = TestDriver.createTaskCommand(
+        variables = Variables.createVariables()
+          .putValue("taskLocalOnlyVariable", "only-value")
+          .putValue("overriddenVariable", "local-value")
+      )
+    }
 
-    verifyNoMoreInteractions(commandListGateway)
+    assertAndExecuteCommandSendingJob()
+
+    verify(commandListGateway).sendToGateway(
+      listOf(createCommand)
+    )
 
   }
 
-  @AfterTransaction
-  fun `after all`() {
-    val jobs = managementService.createJobQuery().list()
-    Assertions.assertThat(jobs).hasSize(1)
+  private fun assertAndExecuteCommandSendingJob() {
+    doInTransaction {
+      val jobs = managementService.createJobQuery().list()
+      Assertions.assertThat(jobs).hasSize(1)
+      Assertions.assertThat(jobs[0]).isInstanceOf(MessageEntity::class.java)
+      Assertions.assertThat((jobs[0] as MessageEntity).jobHandlerType).isEqualTo("polyflow-engine-task-command-sending")
+
+      Awaitility.waitAtMost(3, TimeUnit.SECONDS).untilAsserted {
+        try {
+          execute(jobs[0])
+        } catch (e: Exception) {
+          // brute force preventing Optimistic locking exception, IllegalStateException (job doesn't exist)
+        }
+        Assertions.assertThat(managementService.createJobQuery().count()).isEqualTo(0)
+      }
+    }
   }
 
+  private fun doInTransaction(operation: Runnable) {
+    txTemplate.execute<Any> {
+      operation.run()
+      null
+    }
+
+  }
 
   @SpringBootApplication
   @EnableProcessApplication
@@ -126,5 +173,15 @@ internal class TaskTxJobSenderITest {
     @Bean
     @Primary
     fun testTxJobAxonCommandGateway(): CommandGateway = mock()
+
+    /**
+     * A task listener that sets some local variables.
+     */
+    @Bean
+    fun setTaskLocalVariables() = TaskListener { delegateTask ->
+      delegateTask.setVariableLocal("taskLocalOnlyVariable", "only-value")
+      delegateTask.setVariableLocal("overriddenVariable", "local-value")
+    }
+
   }
 }
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/resources/application-txjob-sender-itest.yml b/integration/camunda-bpm/taskpool-collector/src/test/resources/application-txjob-sender-itest.yml
index 6c3a453c9..2b0e97a03 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/resources/application-txjob-sender-itest.yml
+++ b/integration/camunda-bpm/taskpool-collector/src/test/resources/application-txjob-sender-itest.yml
@@ -1,5 +1,5 @@
 spring:
-  application.name: txjob-sender-test
+  application.name: collector-test
   jpa:
     open-in-view: true # disable JPA warning
     show-sql: true
@@ -17,7 +17,16 @@ camunda:
 
 polyflow:
   integration:
+    collector:
+      camunda:
+        task:
+          enabled: true
+        process-instance:
+          enabled: false
+        process-definition:
+          enabled: false
     sender:
       task:
         type: txjob
         send-within-transaction: true
+
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/resources/itest/message_start_event.bpmn b/integration/camunda-bpm/taskpool-collector/src/test/resources/itest/message_start_event.bpmn
new file mode 100644
index 000000000..67f5ead53
--- /dev/null
+++ b/integration/camunda-bpm/taskpool-collector/src/test/resources/itest/message_start_event.bpmn
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0xgy7na" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.10.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.19.0">
+  <bpmn:process id="my-id" name="My Process" isExecutable="true" camunda:historyTimeToLive="0">
+    <bpmn:endEvent id="Event_0b6e9a9">
+      <bpmn:incoming>Flow_0rqm55e</bpmn:incoming>
+      <bpmn:incoming>Flow_1k4xpiz</bpmn:incoming>
+    </bpmn:endEvent>
+    <bpmn:sequenceFlow id="Flow_0rqm55e" sourceRef="StartEvent_1" targetRef="Event_0b6e9a9" />
+    <bpmn:sequenceFlow id="Flow_1k4xpiz" sourceRef="Event_0tam437" targetRef="Event_0b6e9a9" />
+    <bpmn:startEvent id="StartEvent_1">
+      <bpmn:outgoing>Flow_0rqm55e</bpmn:outgoing>
+      <bpmn:messageEventDefinition id="MessageEventDefinition_1nlj1v7" messageRef="Message_2t2ssos" />
+    </bpmn:startEvent>
+    <bpmn:startEvent id="Event_0tam437">
+      <bpmn:outgoing>Flow_1k4xpiz</bpmn:outgoing>
+      <bpmn:messageEventDefinition id="MessageEventDefinition_1ft2zip" messageRef="Message_22tfs8s" />
+    </bpmn:startEvent>
+  </bpmn:process>
+  <bpmn:message id="Message_2t2ssos" name="start_message" />
+  <bpmn:message id="Message_22tfs8s" name="another_message" />
+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="my-id">
+      <bpmndi:BPMNShape id="Event_0b6e9a9_di" bpmnElement="Event_0b6e9a9">
+        <dc:Bounds x="272" y="79" width="36" height="36" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Event_0qcrckj_di" bpmnElement="StartEvent_1">
+        <dc:Bounds x="179" y="79" width="36" height="36" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Event_02sa6i2_di" bpmnElement="Event_0tam437">
+        <dc:Bounds x="179" y="152" width="36" height="36" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge id="Flow_0rqm55e_di" bpmnElement="Flow_0rqm55e">
+        <di:waypoint x="215" y="97" />
+        <di:waypoint x="272" y="97" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_1k4xpiz_di" bpmnElement="Flow_1k4xpiz">
+        <di:waypoint x="215" y="170" />
+        <di:waypoint x="244" y="170" />
+        <di:waypoint x="244" y="97" />
+        <di:waypoint x="272" y="97" />
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</bpmn:definitions>
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryITest.kt
index c82f4cebe..525c80690 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryITest.kt
@@ -29,6 +29,7 @@ import org.mockito.kotlin.clearInvocations
 import org.mockito.kotlin.verify
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.annotation.DirtiesContext
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.junit.jupiter.SpringExtension
 import org.springframework.transaction.annotation.Transactional
@@ -39,7 +40,6 @@ import java.time.temporal.ChronoUnit
 import java.util.*
 import java.util.function.Predicate
 
-@ExtendWith(SpringExtension::class)
 @SpringBootTest(
   classes = [TestApplication::class],
   properties = [
@@ -48,6 +48,7 @@ import java.util.function.Predicate
 )
 @ActiveProfiles("itest", "mock-query-emitter")
 @Transactional
+@DirtiesContext
 internal class JpaPolyflowViewServiceDataEntryITest {
 
   private val emittedQueryUpdates: MutableList<QueryUpdate<Any>> = mutableListOf()
@@ -264,7 +265,7 @@ internal class JpaPolyflowViewServiceDataEntryITest {
   fun `should find the entry by filter`() {
     assertResultIsTestEntry1And2(
       jpaPolyflowViewService.query(
-        DataEntriesQuery(filters = listOf("key=value"))
+        DataEntriesQuery(filters = listOf("key=value", "key=value2", "key=value3"))
       )
     )
   }
@@ -307,8 +308,9 @@ internal class JpaPolyflowViewServiceDataEntryITest {
 
   private fun assertResultIsTestEntry1And2(result: QueryResponseMessage<DataEntriesQueryResult>) {
     assertThat(result.payload.elements.size).isEqualTo(2)
-    assertTestDataEntry1(result.payload.elements[0])
-    assertTestDataEntry2(result.payload.elements[1])
+    assertThat(result.payload.elements.map { it.entryId }).containsExactlyInAnyOrder(id, id2)
+    assertTestDataEntry1(result.payload.elements.first { it.entryId == id })
+    assertTestDataEntry2(result.payload.elements.first { it.entryId == id2 })
   }
 
 
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryRevisionSupportITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryRevisionSupportITest.kt
index 7f467a591..b2e0e0278 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryRevisionSupportITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceDataEntryRevisionSupportITest.kt
@@ -39,7 +39,6 @@ import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
 import javax.transaction.Transactional
 
-@ExtendWith(SpringExtension::class)
 @SpringBootTest(
   classes = [TestApplication::class],
   properties = [
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessDefinitionITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessDefinitionITest.kt
index 1793dbeee..4c7ccd9c0 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessDefinitionITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessDefinitionITest.kt
@@ -16,7 +16,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.util.*
 import javax.transaction.Transactional
 
-@ExtendWith(SpringExtension::class)
 @SpringBootTest(
   classes = [TestApplication::class],
   properties = [
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessInstanceITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessInstanceITest.kt
index cd0d770e5..e6d86517f 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessInstanceITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceProcessInstanceITest.kt
@@ -19,7 +19,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.util.*
 import javax.transaction.Transactional
 
-@ExtendWith(SpringExtension::class)
 @SpringBootTest(
   classes = [TestApplication::class],
   properties = [
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt
index dc9d2e3c3..52f4acaea 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt
@@ -12,6 +12,7 @@ import io.holunda.polyflow.view.TaskWithDataEntries
 import io.holunda.polyflow.view.auth.User
 import io.holunda.polyflow.view.jpa.itest.TestApplication
 import io.holunda.polyflow.view.jpa.process.toSourceReference
+import io.holunda.polyflow.view.query.data.DataEntriesForUserQuery
 import io.holunda.polyflow.view.query.task.*
 import org.assertj.core.api.Assertions.assertThat
 import org.axonframework.messaging.MetaData
@@ -22,7 +23,7 @@ import org.camunda.bpm.engine.variable.Variables.createVariables
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.assertThrows
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.atLeast
 import org.mockito.kotlin.clearInvocations
@@ -30,7 +31,6 @@ import org.mockito.kotlin.verify
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.context.ActiveProfiles
-import org.springframework.test.context.junit.jupiter.SpringExtension
 import org.springframework.transaction.annotation.Transactional
 import java.time.Instant
 import java.time.OffsetDateTime
@@ -38,7 +38,6 @@ import java.time.ZoneOffset
 import java.util.*
 import java.util.function.Predicate
 
-@ExtendWith(SpringExtension::class)
 @SpringBootTest(
   classes = [TestApplication::class],
   properties = [
@@ -78,15 +77,6 @@ internal class JpaPolyflowViewServiceTaskITest {
 
   @BeforeEach
   fun `ingest events`() {
-    val payload = mapOf(
-      "key" to "value",
-      "key-int" to 1,
-      "complex" to Pojo(
-        attribute1 = "value",
-        attribute2 = Date.from(now)
-      )
-    )
-
     jpaPolyflowViewService.on(
       event = TaskCreatedEngineEvent(
         id = id,
@@ -94,7 +84,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 1",
         priority = 50,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload()) },
         businessKey = "business-1",
         createTime = Date.from(now),
         candidateUsers = setOf("kermit"),
@@ -109,7 +99,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 1",
         priority = 25,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload()) },
         businessKey = "business-1",
         createTime = Date.from(now),
         candidateUsers = setOf("kermit"),
@@ -125,7 +115,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 2",
         priority = 10,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload()) },
         businessKey = "business-2",
         createTime = Date.from(now),
         candidateUsers = setOf("piggy"),
@@ -140,7 +130,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 2",
         priority = 10,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload()) },
         businessKey = "business-2",
         createTime = Date.from(now),
         assignee = "piggy",
@@ -157,7 +147,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 3",
         priority = 10,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload()) },
         correlations = newCorrelations().apply { put(dataType1, dataId1) },
         businessKey = "business-3",
         createTime = Date.from(now),
@@ -174,7 +164,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         applicationName = "test-application",
         name = "Test Entry 1",
         state = ProcessingType.IN_PROGRESS.of("In progress"),
-        payload = serialize(payload = payload, mapper = objectMapper),
+        payload = serialize(payload = createPayload("dataEntry1"), mapper = objectMapper),
         authorizations = listOf(
           AuthorizationChange.addUser("luffy"),
           AuthorizationChange.addGroup("strawhats")
@@ -197,7 +187,7 @@ internal class JpaPolyflowViewServiceTaskITest {
         name = "task name 4",
         priority = 10,
         sourceReference = processReference().toSourceReference(),
-        payload = createVariables().apply { putAll(payload) },
+        payload = createVariables().apply { putAll(createPayload("otherValue")) },
         correlations = newCorrelations().apply {
           put(dataType1, dataId1)
           put(dataType2, dataId2)
@@ -216,9 +206,9 @@ internal class JpaPolyflowViewServiceTaskITest {
         entryId = dataId2,
         type = "Test",
         applicationName = "test-application",
-        name = "Test Entry 1",
+        name = "Test Entry 2",
         state = ProcessingType.IN_PROGRESS.of("In progress"),
-        payload = serialize(payload = payload, mapper = objectMapper),
+        payload = serialize(payload = createPayload("dataEntry2"), mapper = objectMapper),
         authorizations = listOf(
           AuthorizationChange.addUser("zoro")
         ),
@@ -259,14 +249,14 @@ internal class JpaPolyflowViewServiceTaskITest {
 
   @Test
   fun `should find the task by user with data entries`() {
-    val zoro = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(user = User("zoro", setOf())))
+    val zoro = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(user = User("zoro", setOf()), assignedToMeOnly = false))
     assertThat(zoro.elements).isNotEmpty.hasSize(1)
     assertThat(zoro.elements[0].task.id).isEqualTo(id4)
     assertThat(zoro.elements[0].task.name).isEqualTo("task name 4")
     assertThat(zoro.elements[0].dataEntries).isNotEmpty.hasSize(1)
     assertThat(zoro.elements[0].dataEntries[0].entryId).isEqualTo(dataId2)
 
-    val strawhats = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(user = User("other", setOf("strawhats"))))
+    val strawhats = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(user = User("other", setOf("strawhats")), assignedToMeOnly = false))
     assertThat(strawhats.elements).isNotEmpty.hasSize(2)
     assertThat(strawhats.elements.map { it.task.id }).contains(id3, id4)
     assertThat(strawhats.elements[0].dataEntries).hasSize(1)
@@ -275,6 +265,54 @@ internal class JpaPolyflowViewServiceTaskITest {
     assertThat(strawhats.elements[1].dataEntries[0].entryId).isEqualTo(dataId1)
   }
 
+  @Test
+  fun `should find the task by user with data entries and sort results correctly`() {
+    val strawhats = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(
+      user = User("other", setOf("strawhats")),
+      sort = "+name",
+      assignedToMeOnly = false
+    ))
+    val strawhatsInverse = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(
+      user = User("other", setOf("strawhats")),
+      sort = "-name",
+      assignedToMeOnly = false
+    ))
+
+    assertThat(strawhats.elements).isNotEmpty.hasSize(2)
+    assertThat(strawhats.elements.map { it.task.id }).containsExactly(id3, id4)
+    assertThat(strawhatsInverse.elements).isNotEmpty.hasSize(2)
+    assertThat(strawhatsInverse.elements.map { it.task.id }).containsExactly(id4, id3)
+  }
+
+  @Test
+  fun `should not execute query because of wrong sort`() {
+    val user = User("other", setOf("strawhats"))
+    assertThat(assertThrows<IllegalArgumentException> {
+      jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(
+        user = user,
+        sort = "+createdTime", // entity property
+        assignedToMeOnly = false
+      ))
+    }.message).startsWith("Sort parameter must be one of ").endsWith(" but it was createdTime.")
+
+    assertThat(assertThrows<IllegalArgumentException> {
+      jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(
+        user = user,
+        sort = "+candidateUsers", // unsupported by JPA view
+        assignedToMeOnly = false
+      ))
+    }.message).isEqualTo("'candidateUsers' is not supported for sorting in JPA View")
+
+    assertThat(assertThrows<IllegalArgumentException> {
+      jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(
+        user = user,
+        sort = "*name", // wrong order
+        assignedToMeOnly = false
+      ))
+    }.message).isEqualTo("Sort must start either with '+' or '-' but it was starting with '*'")
+
+  }
+
   @Test
   fun `should find the task by group with data entries`() {
     val strawhats = jpaPolyflowViewService.query(TasksWithDataEntriesForGroupQuery(user = User("some", setOf("strawhats")), includeAssigned = false))
@@ -295,15 +333,76 @@ internal class JpaPolyflowViewServiceTaskITest {
 
   @Test
   fun `should find the task by user`() {
-    val kermit = jpaPolyflowViewService.query(TasksForUserQuery(user = User("kermit", setOf())))
+    val kermit = jpaPolyflowViewService.query(TasksForUserQuery(user = User("kermit", setOf()), assignedToMeOnly = false))
     assertThat(kermit.elements).isNotEmpty
     assertThat(kermit.elements[0].id).isEqualTo(id)
     assertThat(kermit.elements[0].name).isEqualTo("task name 1")
-    val muppets = jpaPolyflowViewService.query(TasksForUserQuery(user = User("other", setOf("muppets"))))
+    val muppets = jpaPolyflowViewService.query(TasksForUserQuery(user = User("other", setOf("muppets")), assignedToMeOnly = false))
     assertThat(muppets.elements).isNotEmpty
     assertThat(muppets.elements[0].id).isEqualTo(id)
   }
 
+  @Test
+  fun `should find the task by user assigned to me`() {
+    val luffy = jpaPolyflowViewService.query(TasksForUserQuery(user = User("luffy", setOf()), assignedToMeOnly = false))
+    assertThat(luffy.elements).isNotEmpty
+    assertThat(luffy.elements[0].id).isEqualTo(id3)
+    assertThat(luffy.elements[0].name).isEqualTo("task name 3")
+
+    val zoro = jpaPolyflowViewService.query(TasksForUserQuery(user = User("zoro", setOf()), assignedToMeOnly = true))
+    assertThat(zoro.elements).isNotEmpty
+    assertThat(zoro.elements[0].id).isEqualTo(id4)
+    assertThat(zoro.elements[0].name).isEqualTo("task name 4")
+
+  }
+
+  @Test
+  fun `should or-compose task payload filters on same attribute`() {
+    val query = jpaPolyflowViewService.query(
+      TasksForUserQuery(
+        user = User("zoro", setOf("strawhats")),
+        assignedToMeOnly = false,
+        filters = listOf("key=value", "key=otherValue", "key=anotherValue")
+      )
+    )
+    assertThat(query.elements).hasSize(2)
+  }
+
+  @Test
+  fun `should or-compose data entry payload filters on same attribute`() {
+    val query = jpaPolyflowViewDataEntryService.query(
+      DataEntriesForUserQuery(
+        user = User("zoro", setOf("strawhats")),
+        filters = listOf("key=dataEntry1", "key=dataEntry2", "key=dataEntryFoo")
+      )
+    )
+    assertThat(query.payload.elements).hasSize(2)
+  }
+
+  @Test
+  fun `should or-compose task attribute filters on same attribute`() {
+    val kermit = jpaPolyflowViewService.query(
+      TasksForUserQuery(
+        user = User("zoro", setOf("strawhats")),
+        assignedToMeOnly = false,
+        filters = listOf("task.businessKey=business-3", "task.businessKey=business-4")
+      )
+    )
+    assertThat(kermit.elements).hasSize(2)
+  }
+
+  @Test
+  fun `should or-compose date entry attribute filters on same attribute`() {
+    val kermit = jpaPolyflowViewService.query(
+      TasksWithDataEntriesForUserQuery(
+        user = User("zoro", setOf("strawhats")),
+        assignedToMeOnly = false,
+        filters = listOf("data.entryId=${dataId1}", "data.entryId=${dataId2}")
+      )
+    )
+    assertThat(kermit.elements).hasSize(2)
+  }
+
   @Test
   fun `should find the task by group`() {
     val unassigned = jpaPolyflowViewService.query(TasksForGroupQuery(user = User("other", setOf("muppets")), includeAssigned = false))
@@ -315,6 +414,19 @@ internal class JpaPolyflowViewServiceTaskITest {
     assertThat(assigned.elements[0].name).isEqualTo("task name 1")
   }
 
+  @Test
+  fun `should find the task by candidate user and group`() {
+    val unassigned = jpaPolyflowViewService.query(TasksForCandidateUserAndGroupQuery(user = User("zoro", setOf("muppets")), includeAssigned = false))
+    assertThat(unassigned.elements).isEmpty()
+
+    val assigned = jpaPolyflowViewService.query(TasksForCandidateUserAndGroupQuery(user = User("zoro", setOf("muppets")), includeAssigned = true))
+    assertThat(assigned.elements).hasSize(2)
+    assertThat(assigned.elements[0].id).isEqualTo(id)
+    assertThat(assigned.elements[0].name).isEqualTo("task name 1")
+    assertThat(assigned.elements[1].id).isEqualTo(id4)
+    assertThat(assigned.elements[1].name).isEqualTo("task name 4")
+  }
+
 
   @Test
   fun `query updates are sent`() {
@@ -387,4 +499,14 @@ internal class JpaPolyflowViewServiceTaskITest {
 
   }
 
+  private fun createPayload(value: String = "value"): Map<String, Any> {
+    return mapOf(
+      "key" to value,
+      "key-int" to 1,
+      "complex" to Pojo(
+        attribute1 = "value",
+        attribute2 = Date.from(now)
+      )
+    )
+  }
 }
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/SpecificationTest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/SpecificationTest.kt
index 5c33a2ec1..bb81e5bf9 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/SpecificationTest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/SpecificationTest.kt
@@ -6,6 +6,9 @@ import io.holunda.polyflow.view.jpa.auth.AuthorizationPrincipal.Companion.user
 import io.holunda.polyflow.view.jpa.data.DataEntryRepository
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.springframework.data.domain.Sort.Direction
+import java.lang.NumberFormatException
 
 /**
  * Tests conversion of criteria into JPA Specifications.
@@ -36,6 +39,46 @@ internal class SpecificationTest {
     assertThat(specification).isNotNull
   }
 
+  @Test
+  fun `creates multiple attribute specification for tasks`() {
+    val filters = listOf(
+      "task.priority=50",
+      "task.businessKey=business-1"
+    )
+    val criteria = toCriteria(filters)
+
+    val specification = criteria.toTaskSpecification()
+    assertThat(specification).isNotNull
+  }
+
+  @Test
+  fun `fails with unsupported task attribute in filters`() {
+    val filters = listOf(
+      "task.candidateUsers=foo"
+    )
+    val criteria = toCriteria(filters)
+
+    val error = assertThrows<IllegalArgumentException> {
+     criteria.toTaskSpecification()
+    }
+
+    assertThat(error.message).isEqualTo("JPA View found unsupported task attribute for equals comparison: candidateUsers.")
+  }
+
+  @Test
+  fun `fails with wrong value type for filter attribute`() {
+    val filters = listOf(
+      "task.priority=foo"
+    )
+    val criteria = toCriteria(filters)
+
+    val error = assertThrows<NumberFormatException> {
+      criteria.toTaskSpecification()
+    }
+
+    assertThat(error.message).isEqualTo("For input string: \"foo\"")
+  }
+
   @Test
   fun `creates multiple principal specifications`() {
     val spec = DataEntryRepository.isAuthorizedFor(
@@ -51,11 +94,19 @@ internal class SpecificationTest {
 
   @Test
   fun `creates a paged request`() {
-    val request = pageRequest(page = 15, size = 42, "+name")
-    assertThat(request.pageNumber).isEqualTo(15)
-    assertThat(request.pageSize).isEqualTo(42)
-    assertThat(request.sort.isSorted).isEqualTo(true)
-    assertThat(request.sort.isEmpty).isEqualTo(false)
+    val requestAsc = pageRequest(page = 15, size = 42, "+name")
+    assertThat(requestAsc.pageNumber).isEqualTo(15)
+    assertThat(requestAsc.pageSize).isEqualTo(42)
+    assertThat(requestAsc.sort.isSorted).isEqualTo(true)
+    assertThat(requestAsc.sort.isEmpty).isEqualTo(false)
+    assertThat(requestAsc.sort.getOrderFor("name")!!.direction).isEqualTo(Direction.ASC)
+
+    val requestDesc = pageRequest(page = 15, size = 42, "-name")
+    assertThat(requestDesc.pageNumber).isEqualTo(15)
+    assertThat(requestDesc.pageSize).isEqualTo(42)
+    assertThat(requestDesc.sort.isSorted).isEqualTo(true)
+    assertThat(requestDesc.sort.isEmpty).isEqualTo(false)
+    assertThat(requestDesc.sort.getOrderFor("name")!!.direction).isEqualTo(Direction.DESC)
 
     val unsorted = pageRequest(page = 14, size = 41, null)
     assertThat(unsorted.pageNumber).isEqualTo(14)
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepositoryITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepositoryITest.kt
index 186461c66..e501ebd7f 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepositoryITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/data/DataEntryRepositoryITest.kt
@@ -16,25 +16,24 @@ import org.camunda.bpm.engine.variable.Variables.createVariables
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.extension.ExtendWith
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
 import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
 import org.springframework.data.jpa.domain.Specification.where
 import org.springframework.data.repository.findByIdOrNull
 import org.springframework.test.annotation.DirtiesContext
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.ContextConfiguration
-import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.time.Instant
 import java.util.*
 import javax.persistence.EntityManager
 import javax.transaction.Transactional
 
-@ExtendWith(SpringExtension::class)
 @DataJpaTest(showSql = false)
 @Transactional
 @DirtiesContext
 @ContextConfiguration(classes = [TestApplication::class])
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
 @ActiveProfiles("itest", "mock-query-emitter")
 internal class DataEntryRepositoryITest {
   @Autowired
@@ -48,6 +47,8 @@ internal class DataEntryRepositoryITest {
 
   @BeforeEach
   fun `insert entries`() {
+    dataEntryRepository.deleteAll()
+    entityManager.flush()
 
     val id = UUID.randomUUID().toString()
 
@@ -148,6 +149,13 @@ internal class DataEntryRepositoryITest {
     entityManager.flush()
   }
 
+  @Test
+  fun `loads each protocol entry only once`() {
+    entityManager.clear()
+    val dataEntry = dataEntryRepository.findByIdOrNull(dataEntry.dataEntryId)!!
+    assertThat(dataEntry.protocol.size).isEqualTo(1)
+  }
+
   @Test
   fun `should find data entry by id`() {
     val found = dataEntryRepository.findByIdOrNull(DataEntryId(entryType = dataEntry.dataEntryId.entryType, entryId = dataEntry.dataEntryId.entryId))
@@ -234,13 +242,13 @@ internal class DataEntryRepositoryITest {
   @Test
   fun `should find by filter payload attribute`() {
 
-    val byPayloadFilterByChildKeyNumberValue = dataEntryRepository.findAll(where(hasDataEntryPayloadAttribute("child.key-number", "42")))
+    val byPayloadFilterByChildKeyNumberValue = dataEntryRepository.findAll(where(hasDataEntryPayloadAttribute("child.key-number", listOf("42"))))
     assertThat(byPayloadFilterByChildKeyNumberValue).containsExactlyInAnyOrderElementsOf(listOf(dataEntry, dataEntry2))
 
     val byPayloadFilterByChildKeyValue =
       dataEntryRepository.findAll(
-        where(hasDataEntryPayloadAttribute("child.key", "value"))
-          .and(hasDataEntryPayloadAttribute("id", dataEntry.dataEntryId.entryId))
+        where(hasDataEntryPayloadAttribute("child.key", listOf("value")))
+          .and(hasDataEntryPayloadAttribute("id", listOf(dataEntry.dataEntryId.entryId)))
       )
     assertThat(byPayloadFilterByChildKeyValue).containsExactlyInAnyOrderElementsOf(listOf(dataEntry))
   }
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/itest/FixedH2Dialect.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/itest/FixedH2Dialect.kt
new file mode 100644
index 000000000..951557ba6
--- /dev/null
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/itest/FixedH2Dialect.kt
@@ -0,0 +1,13 @@
+package io.holunda.polyflow.view.jpa.itest
+
+import org.hibernate.dialect.H2Dialect
+import java.sql.Types
+
+class FixedH2Dialect : H2Dialect() {
+  init {
+    // registerColumnType(Types.VARBINARY, "bytea")
+    registerColumnType(Types.BLOB, "bytea")
+    // registerColumnType(Types.LONGVARBINARY, "bytea");
+  }
+
+}
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionRepositoryITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionRepositoryITest.kt
index 4b4f5a242..8371d0305 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionRepositoryITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessDefinitionRepositoryITest.kt
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
 import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.ContextConfiguration
@@ -16,9 +17,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.util.*
 import javax.persistence.EntityManager
 
-@ExtendWith(SpringExtension::class)
 @DataJpaTest(showSql = false)
 @ContextConfiguration(classes = [TestApplicationDataJpa::class])
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
 @ActiveProfiles("itest", "mock-query-emitter")
 class ProcessDefinitionRepositoryITest {
   @Autowired
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceRepositoryITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceRepositoryITest.kt
index 153889462..dbd63fbe8 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceRepositoryITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/process/ProcessInstanceRepositoryITest.kt
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
 import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.ContextConfiguration
@@ -16,9 +17,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.util.*
 import javax.persistence.EntityManager
 
-@ExtendWith(SpringExtension::class)
 @DataJpaTest(showSql = false)
 @ContextConfiguration(classes = [TestApplicationDataJpa::class])
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
 @ActiveProfiles("itest", "mock-query-emitter")
 class ProcessInstanceRepositoryITest {
   @Autowired
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntityTest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntityTest.kt
index b37541307..4f059495e 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntityTest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskEntityTest.kt
@@ -8,7 +8,7 @@ import java.time.Instant
 internal class TaskEntityTest {
 
   @Test
-  internal fun `test toString`() {
+  fun `test toString`() {
     val now = Instant.now()
     val task = emptyTask().apply {
       taskId = "id-123"
diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepositoryITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepositoryITest.kt
index 07b1b6ffc..272f32f52 100644
--- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepositoryITest.kt
+++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepositoryITest.kt
@@ -12,26 +12,38 @@ import io.holunda.polyflow.view.jpa.itest.TestApplicationDataJpa
 import io.holunda.polyflow.view.jpa.payload.PayloadAttribute
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasApplication
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasBusinessKey
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDate
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDateAfter
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasDueDateBefore
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDate
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDateAfter
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasFollowUpDateBefore
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasProcessName
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasTaskPayloadAttribute
 import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAuthorizedFor
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeBusinessKey
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeDescription
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeName
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeProcessName
+import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.likeTextSearch
 import org.assertj.core.api.Assertions.assertThat
 import org.camunda.bpm.engine.variable.Variables.createVariables
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.extension.ExtendWith
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
 import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.ContextConfiguration
-import org.springframework.test.context.junit.jupiter.SpringExtension
-import org.springframework.transaction.annotation.Transactional
+import java.time.LocalDate
+import java.time.ZoneOffset
 import java.util.*
 import javax.persistence.EntityManager
 
-@ExtendWith(SpringExtension::class)
 @DataJpaTest(showSql = false)
 @ContextConfiguration(classes = [TestApplicationDataJpa::class])
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
 @ActiveProfiles("itest", "mock-query-emitter")
 class TaskRepositoryITest {
   @Autowired
@@ -49,6 +61,11 @@ class TaskRepositoryITest {
   lateinit var task1: TaskEntity
   lateinit var task2: TaskEntity
 
+  private val today = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC)
+  private val tomorrow = LocalDate.now().atStartOfDay().plusDays(1).toInstant(ZoneOffset.UTC)
+  private val dayAfterTomorrow = LocalDate.now().atStartOfDay().plusDays(2).toInstant(ZoneOffset.UTC)
+  private val twoDaysAfterTomorrow = LocalDate.now().atStartOfDay().plusDays(3).toInstant(ZoneOffset.UTC)
+
   @BeforeEach
   fun `insert entries`() {
 
@@ -64,20 +81,27 @@ class TaskRepositoryITest {
       )
     }
 
+
     task1 = emptyTask().apply {
       this.taskId = UUID.randomUUID().toString()
       this.name = "task 1"
+      this.businessKey = "ZZZ-1-YYY-2"
+      this.description = "some random description"
       this.authorizedPrincipals = mutableSetOf(user("kermit").toString(), group("muppets").toString())
       this.correlations = mutableSetOf(DataEntryId(entryType = "data-entry", entryId = "id"))
       this.payload = payload.toPayloadJson(objectMapper)
       this.payloadAttributes = payload.toJsonPathsWithValues().map { attr -> PayloadAttribute(attr) }.toMutableSet()
+      this.dueDate = tomorrow
+      this.followUpDate = dayAfterTomorrow
     }
     task2 = emptyTask().apply {
-      taskId = UUID.randomUUID().toString()
-      name = "task 2"
-      businessKey = "business-key"
-      authorizedPrincipals = mutableSetOf(user("ironman").toString(), group("avengers").toString(), group("muppets").toString())
-      sourceReference.applicationName = "other-app"
+      this.taskId = UUID.randomUUID().toString()
+      this.name = "task 2"
+      this.description = "Another than TaSk 1"
+      this.businessKey = "business-key"
+      this.authorizedPrincipals = mutableSetOf(user("ironman").toString(), group("avengers").toString(), group("muppets").toString())
+      this.sourceReference.applicationName = "other-app"
+      this.sourceReference.name = "other-process"
     }
 
     entityManager.persist(task1)
@@ -116,6 +140,52 @@ class TaskRepositoryITest {
     assertThat(result).containsExactlyInAnyOrder(task2)
   }
 
+  @Test
+  fun `should find by task name like`() {
+    val result = taskRepository.findAll(likeName("ask 2"))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+
+  @Test
+  fun `should find by task name like or description like`() {
+    val result = taskRepository.findAll(
+      likeTextSearch("task 1")
+    )
+    assertThat(result).hasSize(2)
+    assertThat(result).containsExactlyInAnyOrder(task1, task2)
+  }
+
+  @Test
+  fun `should find by task description like`() {
+    val result = taskRepository.findAll(likeDescription("random"))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task1)
+  }
+
+  @Test
+  fun `should find by task business like`() {
+    val result = taskRepository.findAll(likeBusinessKey("z-1-y"))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task1)
+  }
+
+
+  @Test
+  fun `should find by process name`() {
+    val result = taskRepository.findAll(hasProcessName("other-process"))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+
+  @Test
+  fun `should find by process name like`() {
+    val result = taskRepository.findAll(likeProcessName("other-process".substring(3, 7)))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+
+
   @Test
   fun `should find by business key`() {
     val result = taskRepository.findAll(hasBusinessKey("business-key"))
@@ -125,12 +195,53 @@ class TaskRepositoryITest {
 
   @Test
   fun `should find by payload attribute`() {
-    val result = taskRepository.findAll(hasTaskPayloadAttribute("complex.child2", "small"))
+    val result = taskRepository.findAll(hasTaskPayloadAttribute("complex.child2", listOf("small")))
     assertThat(result).hasSize(1)
     assertThat(result).containsExactlyInAnyOrder(task1)
 
-    val notfound = taskRepository.findAll(hasTaskPayloadAttribute("complex.child1", "13"))
+    val notfound = taskRepository.findAll(hasTaskPayloadAttribute("complex.child1", listOf("13")))
     assertThat(notfound).isEmpty()
   }
 
+  @Test
+  fun `should find by due date`() {
+    val result = taskRepository.findAll(hasDueDate(tomorrow))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task1)
+  }
+
+  @Test
+  fun `should find by due date before`() {
+    val result = taskRepository.findAll(hasDueDateBefore(tomorrow))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+
+  @Test
+  fun `should find by due date after`() {
+    val result = taskRepository.findAll(hasDueDateAfter(tomorrow))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+  @Test
+  fun `should find by follow-up date`() {
+    val result = taskRepository.findAll(hasFollowUpDate(dayAfterTomorrow))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task1)
+  }
+
+  @Test
+  fun `should find by follow-up date before`() {
+    val result = taskRepository.findAll(hasFollowUpDateBefore(twoDaysAfterTomorrow))
+    assertThat(result).hasSize(2)
+    assertThat(result).containsExactlyInAnyOrder(task1, task2)
+  }
+
+  @Test
+  fun `should find by follow-up date after`() {
+    val result = taskRepository.findAll(hasFollowUpDateAfter(dayAfterTomorrow))
+    assertThat(result).hasSize(1)
+    assertThat(result).containsExactlyInAnyOrder(task2)
+  }
+
 }
diff --git a/view/jpa/src/test/resources/application-itest.yaml b/view/jpa/src/test/resources/application-itest.yaml
index cb7b8d080..926bb0e01 100644
--- a/view/jpa/src/test/resources/application-itest.yaml
+++ b/view/jpa/src/test/resources/application-itest.yaml
@@ -2,9 +2,17 @@ spring:
   jpa:
     open-in-view: true # disable JPA warning
     show-sql: false
-    database-platform: org.hibernate.dialect.H2Dialect
+    database-platform: io.holunda.polyflow.view.jpa.itest.FixedH2Dialect
+    generate-ddl: true
+    hibernate.ddl-auto: create-drop
   datasource:
-    driverClassName: org.h2.Driver
+    url: jdbc:h2:mem:testdb;MODE=PostgreSQL;INIT=create schema if not exists PUBLIC;DB_CLOSE_DELAY=-1;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE
+    username: sa
+    password: sa
+    hikari:
+      connection-test-query: select 1 from dual;
+      schema: PUBLIC
+    driver-class-name: org.h2.Driver
 
 polyflow:
   view:
@@ -13,11 +21,12 @@ polyflow:
 
 logging:
   level:
+    root: INFO
     org.springframework: INFO
     org.axonframework: INFO
     org.hibernate.type: INFO # activate this and generic ROOT logger to see SQL and binding
-    io.holixon.axon.gateway.query: DEBUG
-    io.holunda.polyflow.view.jpa: DEBUG
+    io.holixon.axon.gateway.query: INFO
+    io.holunda.polyflow.view.jpa: INFO
 
 axon:
   axonserver:
diff --git a/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowMongoServiceTestBase.kt b/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowMongoServiceTestBase.kt
index b702ca181..fce9f3ffa 100755
--- a/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowMongoServiceTestBase.kt
+++ b/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowMongoServiceTestBase.kt
@@ -49,10 +49,10 @@ abstract class PolyflowMongoServiceITestBase : SpringScenarioTest<PolyflowGivenS
       .and()
       .tasks_visible_to_candidate_group("muppetshow", listOf(expected))
       .and()
-      .query_updates_have_been_emitted(TasksForUserQuery(User("kermit", setOf("muppetshow"))), expected)
+      .query_updates_have_been_emitted(TasksForUserQuery(assignedToMeOnly = false, user = User("kermit", setOf("muppetshow"))), expected)
       .and()
       .query_updates_have_been_emitted(
-        TasksWithDataEntriesForUserQuery(User("kermit", setOf("muppetshow"))),
+        TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User("kermit", setOf("muppetshow"))),
         expected.withDataEntries()
       )
   }
@@ -75,7 +75,7 @@ abstract class PolyflowMongoServiceITestBase : SpringScenarioTest<PolyflowGivenS
       .task_is_created(TestTaskData(id = "some-id", assignee = "kermit").asTask())
       .and()
       .query_updates_have_been_emitted(
-        query = TasksForUserQuery(User("kermit", setOf("muppetshow"))),
+        query = TasksForUserQuery(assignedToMeOnly = false, user = User("kermit", setOf("muppetshow"))),
         TestTaskData(id = "some-id", assignee = "kermit").asTask()
       )
   }
@@ -162,7 +162,7 @@ abstract class PolyflowMongoServiceITestBase : SpringScenarioTest<PolyflowGivenS
 //      .and()
 //      .query_updates_have_been_emitted(TasksForUserQuery(User("gonzo", setOf("muppetshow"))), testTaskData.asTask().copy(candidateGroups = setOf("simpsons")))
       .and()
-      .query_updates_have_been_emitted(TasksForUserQuery(User("lisa", setOf("simpsons"))), testTaskData.asTask().copy(candidateGroups = setOf("simpsons")))
+      .query_updates_have_been_emitted(TasksForUserQuery(assignedToMeOnly = false, user = User("lisa", setOf("simpsons"))), testTaskData.asTask().copy(candidateGroups = setOf("simpsons")))
   }
 
   @Test
@@ -193,7 +193,7 @@ abstract class PolyflowMongoServiceITestBase : SpringScenarioTest<PolyflowGivenS
 //      .query_updates_have_been_emitted(TasksForUserQuery(kermit), TestTaskData(id = "some-id", candidateUsers = setOf("gonzo")).asTask())
       .and()
       .query_updates_have_been_emitted(
-        TasksForUserQuery(User("gonzo", setOf("muppets"))),
+        TasksForUserQuery(assignedToMeOnly = false, user = User("gonzo", setOf("muppets"))),
         TestTaskData(id = "some-id", candidateUsers = setOf("gonzo")).asTask()
       )
   }
@@ -215,7 +215,7 @@ abstract class PolyflowMongoServiceITestBase : SpringScenarioTest<PolyflowGivenS
     then()
       .task_does_not_exist("some-id")
       .and()
-      .query_updates_have_been_emitted(TasksForUserQuery(User("kermit", setOf("muppetshow"))), TestTaskData(id = "some-id", deleted = true).asTask())
+      .query_updates_have_been_emitted(TasksForUserQuery(assignedToMeOnly = false, user = User("kermit", setOf("muppetshow"))), TestTaskData(id = "some-id", deleted = true).asTask())
   }
 
 
diff --git a/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowStages.kt b/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowStages.kt
index 8b4047940..ba5b08982 100644
--- a/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowStages.kt
+++ b/view/mongo/src/test/kotlin/io/holunda/polyflow/view/mongo/service/PolyflowStages.kt
@@ -157,7 +157,7 @@ class PolyflowThenStage<SELF : PolyflowThenStage<SELF>> : PolyflowStage<SELF>()
 
   @As("tasks with payload with ids \$taskIds are visible to \$user")
   fun tasks_with_payload_are_visible_to(user: User, vararg taskIds: String) = step {
-    val taskResponse = service.query(TasksWithDataEntriesForUserQuery(user = user, page = 1, size = 100)).join()
+    val taskResponse = service.query(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = user, page = 1, size = 100)).join()
     assertThat(taskResponse.elements.map { it.task.id }).containsExactlyElementsOf(taskIds.asIterable())
   }
 
@@ -191,17 +191,17 @@ class PolyflowThenStage<SELF : PolyflowThenStage<SELF>> : PolyflowStage<SELF>()
   }
 
   fun tasks_visible_to_assignee_or_candidate_user(username: String, expectedTasks: List<Task>) = step {
-    val result = service.query(TasksForUserQuery(User(username = username, groups = emptySet()))).join()
+    val result = service.query(TasksForUserQuery(assignedToMeOnly = false, user = User(username = username, groups = emptySet()))).join()
     assertThat(result.elements).containsExactlyElementsOf(expectedTasks)
   }
 
   fun data_entries_visible_to_user(username: String, expectedDataEntries: List<DataEntry>) = step {
-    val result = service.query(DataEntriesForUserQuery(User(username = username, groups = emptySet()))).join()
+    val result = service.query(DataEntriesForUserQuery(user = User(username = username, groups = emptySet()))).join()
     assertThat(result.elements).containsExactlyElementsOf(expectedDataEntries)
   }
 
   fun tasks_visible_to_candidate_group(groupName: String, expectedTasks: List<Task>) = step {
-    val result = service.query(TasksForUserQuery(User(username = "<unmet>", groups = setOf(groupName)))).join()
+    val result = service.query(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "<unmet>", groups = setOf(groupName)))).join()
     assertThat(result.elements).containsExactlyElementsOf(expectedTasks)
   }
 
@@ -237,7 +237,7 @@ class PolyflowThenStage<SELF : PolyflowThenStage<SELF>> : PolyflowStage<SELF>()
   }
 
   fun task_is_not_found_for_user(taskId: String, assignee: String) = step {
-    val result = service.query(TasksForUserQuery(User(assignee, setOf()))).join()
+    val result = service.query(TasksForUserQuery(assignedToMeOnly = false, user = User(assignee, setOf()))).join()
     assertThat(result.elements.map { it.id }).doesNotContain(taskId)
   }
 
diff --git a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt
index 732b273fe..8e30fad6c 100644
--- a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt
+++ b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt
@@ -102,8 +102,8 @@ class SimpleTaskPoolWhenStage<SELF : SimpleTaskPoolWhenStage<SELF>> : AbstractSi
   @ProvidedScenarioState(resolution = ScenarioState.Resolution.NAME)
   private var returnedTasksForApplication = TaskQueryResult(listOf())
 
-  private fun query(page: Int, size: Int) = TasksWithDataEntriesForUserQuery(User("kermit", setOf()), page, size)
-  private fun filterQuery(sort: String, filters: List<String>) = TasksForUserQuery(user = User("kermit", setOf()), filters = filters, sort = sort)
+  private fun query(page: Int, size: Int) = TasksWithDataEntriesForUserQuery(User("kermit", setOf()), true, page, size)
+  private fun filterQuery(sort: String, filters: List<String>) = TasksForUserQuery(assignedToMeOnly = false, user = User("kermit", setOf()), filters = filters, sort = sort)
 
   @As("Page $ is queried with a page size of $")
   fun page_is_queried(page: Int, size: Int) = step {
@@ -122,7 +122,7 @@ class SimpleTaskPoolWhenStage<SELF : SimpleTaskPoolWhenStage<SELF>> : AbstractSi
 
   @As("Tasks are queried with filter $")
   fun tasks_are_queried(filters: List<String>) = step {
-    queriedTasks.addAll(simpleTaskPoolService.query(filterQuery("+name", filters)).elements.map { TaskWithDataEntries(it) })
+    queriedTasks.addAll(simpleTaskPoolService.query(filterQuery("+createdDate", filters)).elements.map { TaskWithDataEntries(it) })
   }
 
   @As("All tasks are queried with filter $")
@@ -214,7 +214,7 @@ class SimpleTaskPoolThenStage<SELF : SimpleTaskPoolThenStage<SELF>> : AbstractSi
   }
 
   fun task_is_not_found_for_user(userId: String, taskId: String) = step {
-    val result = simpleTaskPoolService.query(TasksForUserQuery(User(userId, setOf())))
+    val result = simpleTaskPoolService.query(TasksForUserQuery(assignedToMeOnly = false, user = User(username = userId, groups = setOf())))
     assertThat(result.elements.map { it.id }).doesNotContain(taskId)
   }
 
diff --git a/view/view-api/src/test/kotlin/filter/FilterTest.kt b/view/view-api/src/test/kotlin/filter/FilterTest.kt
index 033094ad9..8b6cb30e2 100755
--- a/view/view-api/src/test/kotlin/filter/FilterTest.kt
+++ b/view/view-api/src/test/kotlin/filter/FilterTest.kt
@@ -84,8 +84,11 @@ class FilterTest {
   fun `should classify properties`() {
     assertThat(isTaskAttribute("task.id")).isTrue
     assertThat(isTaskAttribute("task.name")).isTrue
-    assertThat(isTaskAttribute("task.name")).isTrue
+    assertThat(isTaskAttribute("task.followUpDate")).isTrue
     assertThat(isTaskAttribute("task.dueDate")).isTrue
+    assertThat(isTaskAttribute("task.processName")).isTrue
+    assertThat(isTaskAttribute("task.textSearch")).isTrue
+    assertThat(isTaskAttribute("task.businessKey")).isTrue
 
     assertThat(isTaskAttribute("task.")).isFalse
     assertThat(isTaskAttribute("assignee")).isFalse
@@ -115,6 +118,20 @@ class FilterTest {
     }
   }
 
+  @Test
+  fun `should create task criteria for process name and text search`() {
+    val criteria = toCriteria(listOf("task.processName${LIKE}foo", "task.textSearch${LIKE}bar"))
+    assertThat(criteria).isNotNull
+    assertThat(criteria.size).isEqualTo(2)
+    assertThat(criteria).containsExactlyElementsOf(
+      listOf(
+        Criterion.TaskCriterion("processName", "foo", operator = LIKE),
+        Criterion.TaskCriterion("textSearch", "bar", operator = LIKE),
+      )
+    )
+  }
+
+
   @Test
   fun `should fail to create criteria 2`() {
     assertThatThrownBy {
@@ -134,20 +151,20 @@ class FilterTest {
     assertThat(predicates).isNotNull
     assertThat(predicates.taskAttributePredicate).isNotNull
 
-    val criteriaWithSpaces = toCriteria(listOf("task.name ${EQUALS} some"))
+    val criteriaWithSpaces = toCriteria(listOf("task.name $EQUALS some"))
     val predicatesTrimmed = createTaskPredicates(criteriaWithSpaces)
     assertThat(predicatesTrimmed).isNotNull
     assertThat(predicatesTrimmed.taskAttributePredicate).isNotNull
   }
 
 
+  @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
   @Test
   fun `should create predicates`() {
 
     val criteria = toCriteria(filtersList)
     val predicates = createTaskPredicates(criteria)
 
-    assertThat(predicates).isNotNull
     assertThat(predicates.taskAttributePredicate).isNotNull
     assertThat(predicates.taskPayloadPredicate).isNotNull
 
diff --git a/view/view-api/src/test/kotlin/query/PageableSortableQueryTest.kt b/view/view-api/src/test/kotlin/query/PageableSortableQueryTest.kt
new file mode 100644
index 000000000..7d8698c0a
--- /dev/null
+++ b/view/view-api/src/test/kotlin/query/PageableSortableQueryTest.kt
@@ -0,0 +1,74 @@
+package io.holunda.polyflow.view.query
+
+import io.holunda.polyflow.view.DataEntry
+import io.holunda.polyflow.view.Task
+import io.holunda.polyflow.view.auth.User
+import io.holunda.polyflow.view.query.data.DataEntriesQuery
+import io.holunda.polyflow.view.query.task.AllTasksQuery
+import io.holunda.polyflow.view.query.task.TasksWithDataEntriesForGroupQuery
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import kotlin.reflect.full.declaredMemberProperties
+
+internal class PageableSortableQueryTest {
+
+  private val createTimeAsc = "+createTime"
+  private val nameDesc = "-name"
+  private val badField = "+someTaskField"
+  private val badOrdering = "*createTime"
+  private val user = User(username = "kermit", groups = setOf())
+
+  @Test
+  fun should_sanitize_task_query() {
+    assertThat(AllTasksQuery(sort = createTimeAsc).apply { sanitizeSort(Task::class) }.sort).isEqualTo(createTimeAsc)
+    assertThat(AllTasksQuery(sort = null).apply { sanitizeSort(Task::class) }.sort).isNull()
+    val badFieldException = assertThrows<IllegalArgumentException> { AllTasksQuery(sort = badField).apply { sanitizeSort(Task::class) } }
+    assertThat(badFieldException.message).isEqualTo(
+      "Sort parameter must be one of ${Task::class.declaredMemberProperties.joinToString(", ") { it.name }} but it was ${
+        badField.substring(
+          1
+        )
+      }."
+    )
+
+    val badOperatorException = assertThrows<IllegalArgumentException> { AllTasksQuery(sort = badOrdering).apply { sanitizeSort(Task::class) } }
+    assertThat(badOperatorException.message).isEqualTo("Sort must start either with '+' or '-' but it was starting with '*'")
+  }
+
+  @Test
+  fun should_sanitize_task_with_dataentry_query() {
+    assertThat(TasksWithDataEntriesForGroupQuery(user = user, sort = createTimeAsc).apply { sanitizeSort(Task::class) }.sort).isEqualTo(createTimeAsc)
+    assertThat(TasksWithDataEntriesForGroupQuery(user = user, sort = null).apply { sanitizeSort(Task::class) }.sort).isNull()
+    val badFieldException =
+      assertThrows<IllegalArgumentException> { TasksWithDataEntriesForGroupQuery(user = user, sort = badField).apply { sanitizeSort(Task::class) } }
+    assertThat(badFieldException.message).isEqualTo(
+      "Sort parameter must be one of ${Task::class.declaredMemberProperties.joinToString(", ") { it.name }} but it was ${
+        badField.substring(
+          1
+        )
+      }."
+    )
+
+    val badOperatorException =
+      assertThrows<IllegalArgumentException> { TasksWithDataEntriesForGroupQuery(user = user, sort = badOrdering).apply { sanitizeSort(Task::class) } }
+    assertThat(badOperatorException.message).isEqualTo("Sort must start either with '+' or '-' but it was starting with '*'")
+  }
+
+  @Test
+  fun should_sanitize_dataentry_query() {
+    assertThat(DataEntriesQuery(sort = nameDesc).apply { sanitizeSort(DataEntry::class) }.sort).isEqualTo(nameDesc)
+    assertThat(DataEntriesQuery(sort = null).apply { sanitizeSort(DataEntry::class) }.sort).isNull()
+    val badFieldException = assertThrows<IllegalArgumentException> { DataEntriesQuery(sort = badField).apply { sanitizeSort(DataEntry::class) } }
+    assertThat(badFieldException.message).isEqualTo(
+      "Sort parameter must be one of ${DataEntry::class.declaredMemberProperties.joinToString(", ") { it.name }} but it was ${
+        badField.substring(
+          1
+        )
+      }."
+    )
+
+    val badOperatorException = assertThrows<IllegalArgumentException> { DataEntriesQuery(sort = badOrdering).apply { sanitizeSort(DataEntry::class) } }
+    assertThat(badOperatorException.message).isEqualTo("Sort must start either with '+' or '-' but it was starting with '*'")
+  }
+}
diff --git a/view/view-api/src/test/kotlin/query/task/TaskWithDataEntriesForUserQueryTest.kt b/view/view-api/src/test/kotlin/query/task/TaskWithDataEntriesForUserQueryTest.kt
index 994cd0be8..74f8ccaca 100644
--- a/view/view-api/src/test/kotlin/query/task/TaskWithDataEntriesForUserQueryTest.kt
+++ b/view/view-api/src/test/kotlin/query/task/TaskWithDataEntriesForUserQueryTest.kt
@@ -38,10 +38,10 @@ class TaskWithDataEntriesForUserQueryTest {
 
   @Test
   fun `should filter by user`() {
-    assertThat(TasksWithDataEntriesForUserQuery(user = User(username = "kermit", groups = setOf("muppets"))).applyFilter(taskWithDataEntries)).isTrue
-    assertThat(TasksWithDataEntriesForUserQuery(user = User(username = "kermit", groups = setOf())).applyFilter(taskWithDataEntries)).isTrue
-    assertThat(TasksWithDataEntriesForUserQuery(user = User(username = "piggy", groups = setOf())).applyFilter(taskWithDataEntries)).isTrue
-    assertThat(TasksWithDataEntriesForUserQuery(user = User(username = "ironman", groups = setOf("muppets"))).applyFilter(taskWithDataEntries)).isTrue
-    assertThat(TasksWithDataEntriesForUserQuery(user = User(username = "ironman", groups = setOf("avengers"))).applyFilter(taskWithDataEntries)).isFalse
+    assertThat(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User(username = "kermit", groups = setOf("muppets"))).applyFilter(taskWithDataEntries)).isTrue
+    assertThat(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User(username = "kermit", groups = setOf())).applyFilter(taskWithDataEntries)).isTrue
+    assertThat(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User(username = "piggy", groups = setOf())).applyFilter(taskWithDataEntries)).isTrue
+    assertThat(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User(username = "ironman", groups = setOf("muppets"))).applyFilter(taskWithDataEntries)).isTrue
+    assertThat(TasksWithDataEntriesForUserQuery(assignedToMeOnly = false, user = User(username = "ironman", groups = setOf("avengers"))).applyFilter(taskWithDataEntries)).isFalse
   }
 }
diff --git a/view/view-api/src/test/kotlin/query/task/TasksForUserQueryTest.kt b/view/view-api/src/test/kotlin/query/task/TasksForUserQueryTest.kt
index cb9b7a3d3..e93c4819c 100644
--- a/view/view-api/src/test/kotlin/query/task/TasksForUserQueryTest.kt
+++ b/view/view-api/src/test/kotlin/query/task/TasksForUserQueryTest.kt
@@ -25,10 +25,10 @@ class TasksForUserQueryTest {
 
   @Test
   fun `should filter by user`() {
-    assertThat(TasksForUserQuery(user = User(username = "kermit", groups = setOf("muppets"))).applyFilter(task)).isTrue
-    assertThat(TasksForUserQuery(user = User(username = "kermit", groups = setOf())).applyFilter(task)).isTrue
-    assertThat(TasksForUserQuery(user = User(username = "piggy", groups = setOf())).applyFilter(task)).isTrue
-    assertThat(TasksForUserQuery(user = User(username = "ironman", groups = setOf("muppets"))).applyFilter(task)).isTrue
-    assertThat(TasksForUserQuery(user = User(username = "ironman", groups = setOf("avengers"))).applyFilter(task)).isFalse
+    assertThat(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "kermit", groups = setOf("muppets"))).applyFilter(task)).isTrue
+    assertThat(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "kermit", groups = setOf())).applyFilter(task)).isTrue
+    assertThat(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "piggy", groups = setOf())).applyFilter(task)).isTrue
+    assertThat(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "ironman", groups = setOf("muppets"))).applyFilter(task)).isTrue
+    assertThat(TasksForUserQuery(assignedToMeOnly = false, user = User(username = "ironman", groups = setOf("avengers"))).applyFilter(task)).isFalse
   }
 }