diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 194b4911cb95ebbac632c98c6bbadcd6bd8eba7f..31a4771ea0d84a6308648de375c9f26a71532e09 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -90,7 +90,7 @@ maven-test:
     # override the policy
     policy: pull
   script:
-    - ./mvnw -ntp verify -P-webapp -Dmaven.repo.local=$MAVEN_USER_HOME -Dapplication.gitlab.guestAccessToken=$APPLICATION_GITLAB_GENERALACCESSTOKEN -Dapplication.gitlab.adminAccessToken=$APPLICATION_GITLAB_ADMINACCESSTOKEN -Dspring.profiles.active=testcontainers -Dlogging.level.ROOT=ERROR -Dlogging.level.org.zalando=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.at.ac.uibk.gitsearch=WARN -Dlogging.level.org.springframework=WARN -Dlogging.level.org.springframework.web=WARN -Dlogging.level.org.springframework.security=OFF -Dlogging.level.org.hibernate.SQL=WARN
+    - ./mvnw -ntp verify -P-webapp -Dmaven.repo.local=$MAVEN_USER_HOME -Dapplication.gitlab.guestAccessToken=$APPLICATION_GITLAB_GENERALACCESSTOKEN -Dapplication.gitlab.adminAccessToken=$APPLICATION_GITLAB_ADMINACCESSTOKEN -Dspring.profiles.active=testcontainers
   allow_failure: true
   artifacts:
     reports:
diff --git a/package-lock.json b/package-lock.json
index 81c14953190df06ffc54d364099418c10f801ba2..502a65416f6f32251843c6a1e2351d4cce746078 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -480,12 +480,6 @@
         "node": ">=6.9.0"
       }
     },
-    "node_modules/@angular-devkit/build-angular/node_modules/@types/estree": {
-      "version": "0.0.50",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
-      "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
-      "dev": true
-    },
     "node_modules/@angular-devkit/build-angular/node_modules/ajv": {
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
@@ -789,15 +783,6 @@
         "win32"
       ]
     },
-    "node_modules/@angular-devkit/build-angular/node_modules/events": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
-      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8.x"
-      }
-    },
     "node_modules/@angular-devkit/build-angular/node_modules/glob-parent": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -983,102 +968,6 @@
         "semver": "bin/semver.js"
       }
     },
-    "node_modules/@angular-devkit/build-angular/node_modules/webpack": {
-      "version": "5.65.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz",
-      "integrity": "sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw==",
-      "dev": true,
-      "dependencies": {
-        "@types/eslint-scope": "^3.7.0",
-        "@types/estree": "^0.0.50",
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/wasm-edit": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1",
-        "acorn": "^8.4.1",
-        "acorn-import-assertions": "^1.7.6",
-        "browserslist": "^4.14.5",
-        "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.8.3",
-        "es-module-lexer": "^0.9.0",
-        "eslint-scope": "5.1.1",
-        "events": "^3.2.0",
-        "glob-to-regexp": "^0.4.1",
-        "graceful-fs": "^4.2.4",
-        "json-parse-better-errors": "^1.0.2",
-        "loader-runner": "^4.2.0",
-        "mime-types": "^2.1.27",
-        "neo-async": "^2.6.2",
-        "schema-utils": "^3.1.0",
-        "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.1.3",
-        "watchpack": "^2.3.1",
-        "webpack-sources": "^3.2.2"
-      },
-      "bin": {
-        "webpack": "bin/webpack.js"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      },
-      "peerDependenciesMeta": {
-        "webpack-cli": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv": {
-      "version": "6.12.6",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-      "dev": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
-      }
-    },
-    "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv-keywords": {
-      "version": "3.5.2",
-      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
-      "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
-      "dev": true,
-      "peerDependencies": {
-        "ajv": "^6.9.1"
-      }
-    },
-    "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true
-    },
-    "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/schema-utils": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-      "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.8",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
     "node_modules/@angular-devkit/build-webpack": {
       "version": "0.1301.2",
       "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1301.2.tgz",
@@ -25100,7 +24989,6 @@
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz",
       "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==",
       "dev": true,
-      "peer": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.0",
         "@types/estree": "^0.0.50",
@@ -25531,15 +25419,13 @@
       "version": "0.0.50",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
       "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
-      "dev": true,
-      "peer": true
+      "dev": true
     },
     "node_modules/webpack/node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
       "dev": true,
-      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "fast-json-stable-stringify": "^2.0.0",
@@ -25556,7 +25442,6 @@
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
       "dev": true,
-      "peer": true,
       "peerDependencies": {
         "ajv": "^6.9.1"
       }
@@ -25566,7 +25451,6 @@
       "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
       "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
       "dev": true,
-      "peer": true,
       "engines": {
         "node": ">=0.8.x"
       }
@@ -25575,15 +25459,13 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true,
-      "peer": true
+      "dev": true
     },
     "node_modules/webpack/node_modules/schema-utils": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
       "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
       "dev": true,
-      "peer": true,
       "dependencies": {
         "@types/json-schema": "^7.0.8",
         "ajv": "^6.12.5",
@@ -26712,12 +26594,6 @@
             "@babel/types": "^7.16.0"
           }
         },
-        "@types/estree": {
-          "version": "0.0.50",
-          "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
-          "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
-          "dev": true
-        },
         "ajv": {
           "version": "8.8.2",
           "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
@@ -26895,12 +26771,6 @@
           "dev": true,
           "optional": true
         },
-        "events": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
-          "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
-          "dev": true
-        },
         "glob-parent": {
           "version": "6.0.2",
           "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -27039,76 +26909,6 @@
               "dev": true
             }
           }
-        },
-        "webpack": {
-          "version": "5.65.0",
-          "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz",
-          "integrity": "sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw==",
-          "dev": true,
-          "requires": {
-            "@types/eslint-scope": "^3.7.0",
-            "@types/estree": "^0.0.50",
-            "@webassemblyjs/ast": "1.11.1",
-            "@webassemblyjs/wasm-edit": "1.11.1",
-            "@webassemblyjs/wasm-parser": "1.11.1",
-            "acorn": "^8.4.1",
-            "acorn-import-assertions": "^1.7.6",
-            "browserslist": "^4.14.5",
-            "chrome-trace-event": "^1.0.2",
-            "enhanced-resolve": "^5.8.3",
-            "es-module-lexer": "^0.9.0",
-            "eslint-scope": "5.1.1",
-            "events": "^3.2.0",
-            "glob-to-regexp": "^0.4.1",
-            "graceful-fs": "^4.2.4",
-            "json-parse-better-errors": "^1.0.2",
-            "loader-runner": "^4.2.0",
-            "mime-types": "^2.1.27",
-            "neo-async": "^2.6.2",
-            "schema-utils": "^3.1.0",
-            "tapable": "^2.1.1",
-            "terser-webpack-plugin": "^5.1.3",
-            "watchpack": "^2.3.1",
-            "webpack-sources": "^3.2.2"
-          },
-          "dependencies": {
-            "ajv": {
-              "version": "6.12.6",
-              "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-              "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-              "dev": true,
-              "requires": {
-                "fast-deep-equal": "^3.1.1",
-                "fast-json-stable-stringify": "^2.0.0",
-                "json-schema-traverse": "^0.4.1",
-                "uri-js": "^4.2.2"
-              }
-            },
-            "ajv-keywords": {
-              "version": "3.5.2",
-              "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
-              "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
-              "dev": true,
-              "requires": {}
-            },
-            "json-schema-traverse": {
-              "version": "0.4.1",
-              "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-              "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-              "dev": true
-            },
-            "schema-utils": {
-              "version": "3.1.1",
-              "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-              "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
-              "dev": true,
-              "requires": {
-                "@types/json-schema": "^7.0.8",
-                "ajv": "^6.12.5",
-                "ajv-keywords": "^3.5.2"
-              }
-            }
-          }
         }
       }
     },
@@ -45238,7 +45038,6 @@
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz",
       "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==",
       "dev": true,
-      "peer": true,
       "requires": {
         "@types/eslint-scope": "^3.7.0",
         "@types/estree": "^0.0.50",
@@ -45270,15 +45069,13 @@
           "version": "0.0.50",
           "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
           "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
-          "dev": true,
-          "peer": true
+          "dev": true
         },
         "ajv": {
           "version": "6.12.6",
           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
           "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
           "dev": true,
-          "peer": true,
           "requires": {
             "fast-deep-equal": "^3.1.1",
             "fast-json-stable-stringify": "^2.0.0",
@@ -45291,29 +45088,25 @@
           "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
           "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
           "dev": true,
-          "peer": true,
           "requires": {}
         },
         "events": {
           "version": "3.3.0",
           "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
           "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
-          "dev": true,
-          "peer": true
+          "dev": true
         },
         "json-schema-traverse": {
           "version": "0.4.1",
           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
           "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-          "dev": true,
-          "peer": true
+          "dev": true
         },
         "schema-utils": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
           "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
           "dev": true,
-          "peer": true,
           "requires": {
             "@types/json-schema": "^7.0.8",
             "ajv": "^6.12.5",
diff --git a/pom.xml b/pom.xml
index c973632f2099aece9f84b9f37454509856403abf..a966234dd493c39776475889e37beb9cec3af22a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
         <jhipster-dependencies.version>7.6.0</jhipster-dependencies.version>
         <!-- The spring-boot version should match the one managed by
         https://mvnrepository.com/artifact/tech.jhipster/jhipster-dependencies/${jhipster-dependencies.version} -->
-        <spring-boot.version>2.6.14</spring-boot.version>
+        <spring-boot.version>2.7.9</spring-boot.version>
         <!-- The hibernate version should match the one managed by
         https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} -->
         <hibernate.version>5.6.4.Final</hibernate.version>
@@ -277,7 +277,6 @@
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
-            <version>2.7.4</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -396,7 +395,22 @@
             <!-- <scope>test</scope> -->
         </dependency>
         <!-- jhipster-needle-maven-add-dependency -->
-    </dependencies>
+        <dependency>
+        	<groupId>co.elastic.clients</groupId>
+        	<artifactId>elasticsearch-java</artifactId>
+        	<version>7.17.9</version>
+        </dependency>
+		<dependency>
+		    <groupId>org.elasticsearch.client</groupId>
+		    <artifactId>elasticsearch-rest-client</artifactId>
+		    <version>7.17.9</version>
+		</dependency>
+        <dependency>
+		  <groupId>jakarta.json</groupId>
+		  <artifactId>jakarta.json-api</artifactId>
+		  <version>2.0.1</version>
+		</dependency>
+	</dependencies>
 
     <build>
         <defaultGoal>spring-boot:run</defaultGoal>
diff --git a/src/main/java/at/ac/uibk/gitsearch/config/ElasticsearchConfiguration.java b/src/main/java/at/ac/uibk/gitsearch/config/ElasticsearchConfiguration.java
index 633af41692291280117b8e4fe49e9dbd06c745ea..0f92221333220045645cdf2691933603eb38359e 100644
--- a/src/main/java/at/ac/uibk/gitsearch/config/ElasticsearchConfiguration.java
+++ b/src/main/java/at/ac/uibk/gitsearch/config/ElasticsearchConfiguration.java
@@ -1,10 +1,29 @@
 package at.ac.uibk.gitsearch.config;
 
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.json.jackson.JacksonJsonpMapper;
+import co.elastic.clients.transport.ElasticsearchTransport;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.time.Instant;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import org.apache.http.HttpHost;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.client.RestClient;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.convert.converter.Converter;
@@ -12,10 +31,14 @@ import org.springframework.data.convert.ReadingConverter;
 import org.springframework.data.convert.WritingConverter;
 import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
 import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
+import org.springframework.util.Assert;
 
 @Configuration
 public class ElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
 
+    @Value("${spring.elasticsearch.uris}")
+    private String elasticSearchUrls;
+
     @Bean
     @Override
     public ElasticsearchCustomConversions elasticsearchCustomConversions() {
@@ -102,4 +125,53 @@ public class ElasticsearchConfiguration extends ElasticsearchConfigurationSuppor
             return LocalDate.parse(source);
         }
     }
+
+    public static class MoreRobustLocalDateTimeConverter extends com.fasterxml.jackson.databind.JsonDeserializer<LocalDateTime> {
+
+        @SuppressWarnings("unused")
+        private final Logger log = LogManager.getLogger(MoreRobustLocalDateTimeConverter.class);
+
+        @Override
+        @SuppressWarnings("PMD.PreserveStackTrace")
+        public LocalDateTime deserialize(com.fasterxml.jackson.core.JsonParser p, DeserializationContext ctxt) throws IOException {
+            try {
+                final String text = p.getText();
+                return LocalDateTime.parse(text, DateTimeFormatter.ISO_ZONED_DATE_TIME);
+            } catch (DateTimeParseException e) {
+                try {
+                    final String text = p.getText();
+                    return LocalDateTime.parse(text);
+                } catch (DateTimeParseException e2) {
+                    throw new IOException(e2);
+                }
+            }
+        }
+    }
+
+    @Bean
+    public ElasticsearchClient getElasticsearchAPIClient() throws MalformedURLException {
+        // Create the transport with a Jackson mapper
+        final JacksonJsonpMapper mapper = new JacksonJsonpMapper();
+        final JavaTimeModule timeModule = new JavaTimeModule();
+        timeModule.addDeserializer(LocalDateTime.class, new ElasticsearchConfiguration.MoreRobustLocalDateTimeConverter());
+
+        mapper.objectMapper().registerModule(timeModule);
+        mapper.objectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+        ElasticsearchTransport transport = new RestClientTransport(buildClient(), mapper);
+
+        // And create the API client
+        return new ElasticsearchClient(transport);
+    }
+
+    private RestClient buildClient() throws MalformedURLException {
+        Assert.hasText(elasticSearchUrls, "[Assertion Failed] At least one host must be set.");
+
+        ArrayList<HttpHost> httpHosts = new ArrayList<>();
+        for (String host : elasticSearchUrls.split(",")) {
+            URL hostUrl = new URL(host);
+            httpHosts.add(new HttpHost(hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol()));
+        }
+        return RestClient.builder(httpHosts.toArray(new HttpHost[0])).build();
+    }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java b/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java
index 40fb99b094ca44bddacc0d2ae531ddc19ddd4656..02afd72eb25bb4771de3373e50792f9a6fe7bc0c 100644
--- a/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java
+++ b/src/main/java/at/ac/uibk/gitsearch/config/SecurityConfiguration.java
@@ -461,11 +461,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
             }
 
             Map<String, Object> claims = new ConcurrentHashMap<>(oidcUser.getUserInfo().getClaims());
-            userDetailsInfo
-                .getUserToken()
-                .ifPresent(token -> {
-                    claims.put(TokenProvider.GITLAB_ACCESS_TOKEN, token.getToken());
-                });
+            userDetailsInfo.getUserToken().ifPresent(token -> claims.put(TokenProvider.GITLAB_ACCESS_TOKEN, token.getToken()));
             OidcUserInfo userInfo = new OidcUserInfo(claims);
 
             oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), userInfo);
diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/StatisticsRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/StatisticsRepository.java
index 1a501cc89a719697140f2417c4a88acde88b927b..324124c5c2c26c31ab84242afdedef385457d6ac 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/StatisticsRepository.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/StatisticsRepository.java
@@ -5,6 +5,7 @@ import java.util.List;
 import java.util.Optional;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
 /**
@@ -16,15 +17,18 @@ public interface StatisticsRepository extends JpaRepository<Statistics, Long> {
 
     Optional<Statistics> findById(Long id);
 
-    @Query(value = "SELECT SUM(u.views) FROM statistics u WHERE u.exercise_id IN (?1)", nativeQuery = true)
-    Optional<Integer> findNumberOfViewsByExerciseIDs(List<String> list);
+    @Query("SELECT SUM(u.views) FROM Statistics u WHERE u.exerciseID IN (:ids)")
+    Optional<Integer> findNumberOfViewsByExerciseIDs(@Param("ids") List<String> exerciseIds);
 
-    @Query(value = "SELECT SUM(u.downloads) FROM statistics u WHERE u.exercise_id IN (?1)", nativeQuery = true)
-    Optional<Integer> findNumberOfDownloadsByExerciseIDs(List<String> list);
+    @Query("SELECT SUM(u.downloads) FROM Statistics u WHERE u.exerciseID IN (:ids)")
+    Optional<Integer> findNumberOfDownloadsByExerciseIDs(@Param("ids") List<String> exerciseIds);
 
-    @Query(value = "SELECT SUM(u.downloads), SUM(u.views) FROM statistics u WHERE u.exercise_id IN (?1)", nativeQuery = true)
-    List<Integer[]> findNumberOfDownloadsAndViewsByExerciseIDs(List<String> list);
+    @Query("SELECT SUM(u.downloads), SUM(u.views) FROM Statistics u WHERE u.exerciseID IN (:ids)")
+    List<Integer[]> findNumberOfDownloadsAndViewsByExerciseIDs(@Param("ids") List<String> exerciseIds);
 
-    @Query(value = "SELECT COUNT(u.exercise_id) FROM watch_list_entry u where exercise_id = ?1", nativeQuery = true)
-    Integer findNumberOfWatchListEntriesByExerciseID(String exerciseID);
+    @Query("SELECT COUNT(u.exerciseId) FROM WatchListEntry u where exerciseId = :id")
+    Integer findNumberOfWatchListEntriesByExerciseID(@Param("id") String exerciseID);
+
+    @Query("SELECT SUM(u.downloads), SUM(u.views) FROM Statistics u")
+    List<Integer[]> findNumberOfDownloadsAndViews();
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImpl.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImpl.java
index c0ae745382e745f9d74c921f8d66dc9bbbf76687..3a8a21ea2663fc835ff084c4e6e2b2c0059a3069 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImpl.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImpl.java
@@ -83,7 +83,6 @@ public class GitFilesRepositoryImpl implements GitFilesRepository {
             .highlighter(highlightBuilder)
             .size(searchInputDTO.getPageSize())
             .from((searchInputDTO.getPage() - 1) * searchInputDTO.getPageSize());
-        //.fetchSource(includes, excludes);
 
         searchRequest.source(sourceBuilder);
 
@@ -98,7 +97,7 @@ public class GitFilesRepositoryImpl implements GitFilesRepository {
             DocumentInfo documentInfo = objectMapper.readValue(searchHit.getSourceAsString(), DocumentInfo.class);
             gitFilesDTOS.add(new GitFilesDTO(documentInfo, searchHit, preTags, postTags));
         }
-        long hitCount = searchResponse.getHits().getTotalHits().value;
+        long hitCount = searchResponse.getHits().getTotalHits() == null ? 0 : searchResponse.getHits().getTotalHits().value;
         return new GitFilesPageDetailsDTO(gitFilesDTOS, hitCount);
     }
 
diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
index 5c8403a1507bd5c72153e9e9afebef2b87862def..c8863935de48a24f6e7ee2697a0f4b8f853e5655 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
@@ -3,75 +3,91 @@ package at.ac.uibk.gitsearch.repository.search;
 import at.ac.uibk.gitsearch.domain.Authority;
 import at.ac.uibk.gitsearch.domain.util.TreeNode;
 import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.ExistsQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.MatchPhrasePrefixQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.PrefixQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import co.elastic.clients.json.JsonData;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import java.io.IOException;
+import java.net.ConnectException;
 import java.text.ParseException;
 import java.time.Instant;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.ConcurrentModificationException;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import java.util.StringTokenizer;
-import java.util.Timer;
-import java.util.TimerTask;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 import javax.annotation.PostConstruct;
 import javax.ws.rs.NotFoundException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.apache.lucene.search.TotalHits;
+import org.codeability.sharing.plugins.api.search.SearchInputDTO;
 import org.codeability.sharing.plugins.api.search.SearchResultDTO;
 import org.codeability.sharing.plugins.api.search.SearchResultsDTO;
 import org.codeability.sharing.plugins.api.search.UserProvidedMetadataDTO.Person;
 import org.codeability.sharing.plugins.api.search.util.ExerciseId;
 import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.RequestOptions.Builder;
-import org.elasticsearch.client.RestHighLevelClient;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.ExistsQueryBuilder;
-import org.elasticsearch.index.query.MultiMatchQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.index.query.TermQueryBuilder;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cache.annotation.Cacheable;
-import org.springframework.context.event.ContextStoppedEvent;
-import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.stereotype.Repository;
 
 @Repository
-@SuppressWarnings({ "PMD.GodClass", "PMD.CyclomaticComplexity", "PMD.TooManyMethods" })
+@SuppressWarnings(
+    {
+        "PMD.GodClass",
+        "PMD.CyclomaticComplexity",
+        "PMD.TooManyMethods",
+        "PMD.CognitiveComplexity",
+        "PMD.NPathComplexity",
+        "PMD.ExcessiveClassLength",
+        "PMD.AvoidDuplicateLiterals",
+    }
+)
 public class MetaDataRepository {
 
-    private final RestHighLevelClient elasticsearchClient;
+    private final ElasticsearchClient elasticsearchAPIClient;
 
     private static final Logger LOGGER = LogManager.getLogger(MetaDataRepository.class);
 
-    private static final long AUTOCOMPLETION_RECHECK_TIMEOUT = 5 * 60 * 1000L; // every 5 Minutes
-    private final Timer autocompletionReloadTimer = new Timer("AutocompletionReloadTimer");
+    private static final int SCROLL_SIZE = 10;
 
     public static final String EXERCISE_BY_ID_CACHE = "at.ac.uibk.gitsearch.repository.search.MetaDataRepository.ExerciseByIdCache";
 
@@ -90,10 +106,17 @@ public class MetaDataRepository {
      * with hit counts E.g. "metadata.creator" -> Kerstin -> {"Kerstin Limbeck" ->
      * 3, "Kerstin Mair" -> 2)}
      */
-    private Map<String, Map<String, Map<String, Integer>>> cachedCompletions = null;
+    private Map<String, Map<String, Map<String, Integer>>> cachedCompletions;
+
+    public MetaDataRepository(ElasticsearchClient elasticsearchAPIClient) {
+        this.elasticsearchAPIClient = elasticsearchAPIClient;
 
-    public MetaDataRepository(RestHighLevelClient elasticsearchClient) {
-        this.elasticsearchClient = elasticsearchClient;
+        cachedCompletions = new ConcurrentHashMap<>();
+        cachedCompletions.put(SearchRepositoryConstants.METADATA_KEYWORDS, new TreeMap<>());
+        cachedCompletions.put(SearchRepositoryConstants.METADATA_CREATOR, new TreeMap<>());
+        cachedCompletions.put(SearchRepositoryConstants.METADATA_CONTRIBUTOR, new TreeMap<>());
+        cachedCompletions.put(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, new TreeMap<>());
+        cachedCompletions.put(SearchRepositoryConstants.METADATA_FORMATS, new TreeMap<>());
     }
 
     /**
@@ -103,7 +126,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getKeywordsAutoComplete(String keyWordPrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getKeywordsAutoComplete(String keyWordPrefix, int max) {
         return getFieldAutoCompletion(keyWordPrefix, SearchRepositoryConstants.METADATA_KEYWORDS, max);
     }
 
@@ -114,7 +137,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getFormatsAutoComplete(String formatPrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getFormatsAutoComplete(String formatPrefix, int max) {
         return getFieldAutoCompletion(formatPrefix, SearchRepositoryConstants.METADATA_FORMATS, max);
     }
 
@@ -125,7 +148,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(String programmingLanguagePrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(String programmingLanguagePrefix, int max) {
         return getFieldAutoCompletion(programmingLanguagePrefix, SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, max);
     }
 
@@ -136,7 +159,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getCreatorAutoComplete(String creatorPrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getCreatorAutoComplete(String creatorPrefix, int max) {
         return getFieldAutoCompletion(creatorPrefix, SearchRepositoryConstants.METADATA_CREATOR, max);
     }
 
@@ -147,7 +170,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getContributorAutoComplete(String contributorPrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getContributorAutoComplete(String contributorPrefix, int max) {
         return getFieldAutoCompletion(contributorPrefix, SearchRepositoryConstants.METADATA_CONTRIBUTOR, max);
     }
 
@@ -158,7 +181,7 @@ public class MetaDataRepository {
      * @return
      * @throws IOException
      */
-    public List<AutoCompleteEntry> getContributorCreatorAutoComplete(String contributorPrefix, int max) throws IOException {
+    public List<AutoCompleteEntry> getContributorCreatorAutoComplete(String contributorPrefix, int max) {
         final List<AutoCompleteEntry> contributors = getContributorAutoComplete(contributorPrefix, max);
         final List<AutoCompleteEntry> creatorAutoComplete = getCreatorAutoComplete(contributorPrefix, max);
         // merging
@@ -189,9 +212,7 @@ public class MetaDataRepository {
      * @throws JsonProcessingException
      * @throws JsonMappingException
      */
-    private List<AutoCompleteEntry> getFieldAutoCompletion(String prefix, final String metaDataField, int maxCount) throws IOException {
-        fillAutoCompletion();
-
+    private List<AutoCompleteEntry> getFieldAutoCompletion(String prefix, final String metaDataField, int maxCount) {
         String lcKeyWordPrefix = prefix.toLowerCase(Locale.getDefault());
         // höllisch kompliziert und höllisch unverständlich mit Streams :-)
         // idea
@@ -222,112 +243,70 @@ public class MetaDataRepository {
 
     }
 
+    @Scheduled(cron = "0 */10 * * * ?") // should run every 10 minutes
     @PostConstruct
-    @SuppressWarnings("PMD")
-    private void setUpAutocompletion() {
-        autocompletionReloadTimer.schedule(
-            new TimerTask() {
-                @Override
-                public void run() {
-                    try {
-                        updateAutocompletionCache();
-                    } catch (IOException | RuntimeException e) {
-                        LOGGER.warn("Cannot refresh Autocompletion Cache", e);
-                    }
-                }
-            },
-            10000L,
-            AUTOCOMPLETION_RECHECK_TIMEOUT
-        );
-    }
-
-    /**
-     * stops the cache reload timer, during shutdown or between tests.
-     *
-     * @param event ContextStoppedEvent
-     */
-    @EventListener
-    protected void stopReloadTimer(ContextStoppedEvent event) {
-        autocompletionReloadTimer.cancel();
-    }
-
-    private void fillAutoCompletion() throws IOException {
+    public void updateAutocompletionCache() throws IOException {
         synchronized (this) {
-            if (cachedCompletions == null) {
-                updateAutocompletionCache();
-            }
-        }
-    }
-
-    @SuppressWarnings({ "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
-    private void updateAutocompletionCache() throws IOException {
-        Map<String, Map<String, Map<String, Integer>>> reloadedCachedCompletions = new ConcurrentHashMap<>();
-        reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_KEYWORDS, new TreeMap<>());
-        reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CREATOR, new TreeMap<>());
-        reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CONTRIBUTOR, new TreeMap<>());
-        reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, new TreeMap<>());
-        reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_FORMATS, new TreeMap<>());
-
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-
-        final int pageSize = 20;
-        int currentPage = 0;
-        boolean tryNextPage = true;
-
-        while (tryNextPage) {
-            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().size(pageSize).from(currentPage++ * pageSize);
-
-            searchRequest.source(sourceBuilder);
-
-            SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
-
-            ObjectMapper objectMapper = getSearchResultObjectMapper();
+            Map<String, Map<String, Map<String, Integer>>> reloadedCachedCompletions = new ConcurrentHashMap<>();
+            reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_KEYWORDS, new TreeMap<>());
+            reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CREATOR, new TreeMap<>());
+            reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CONTRIBUTOR, new TreeMap<>());
+            reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, new TreeMap<>());
+            reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_FORMATS, new TreeMap<>());
 
-            tryNextPage = searchResponse.getHits().getHits().length > 0;
-
-            for (SearchHit searchHit : searchResponse.getHits().getHits()) {
-                final String sourceAsString = filterDateString(searchHit.getSourceAsString());
-                SearchResultDTO entry = objectMapper.readValue(sourceAsString, SearchResultDTO.class);
-                if (entry.getMetadata().getKeyword() != null) {
-                    for (String keyWord : entry.getMetadata().getKeyword()) {
-                        addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_KEYWORDS), split(keyWord), keyWord);
-                    }
-                }
-                if (entry.getMetadata().getFormat() != null) {
-                    for (String format : entry.getMetadata().getFormat()) {
-                        addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_FORMATS), split(format), format);
-                    }
-                }
-                if (entry.getMetadata().getContributor() != null) {
-                    for (Person contributor : entry.getMetadata().getContributor()) {
-                        addTo(
-                            reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CONTRIBUTOR),
-                            split(contributor.getName()),
-                            contributor.getName()
-                        );
-                    }
-                }
-                if (entry.getMetadata().getCreator() != null) {
-                    for (Person creator : entry.getMetadata().getCreator()) {
-                        addTo(
-                            reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CREATOR),
-                            split(creator.getName()),
-                            creator.getName()
-                        );
-                    }
-                }
-                if (entry.getMetadata().getProgrammingLanguage() != null) {
-                    for (String language : entry.getMetadata().getProgrammingLanguage()) {
-                        addTo(
-                            reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES),
-                            Collections.singletonList(language),
-                            language
-                        );
-                    }
-                }
+            try {
+                this.getAllResultsAsStream()
+                    .forEach(entry -> {
+                        if (entry.getMetadata().getKeyword() != null) {
+                            for (String keyWord : entry.getMetadata().getKeyword()) {
+                                addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_KEYWORDS), split(keyWord), keyWord);
+                            }
+                        }
+                        if (entry.getMetadata().getFormat() != null) {
+                            for (String format : entry.getMetadata().getFormat()) {
+                                addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_FORMATS), split(format), format);
+                            }
+                        }
+                        if (entry.getMetadata().getContributor() != null) {
+                            for (Person contributor : entry.getMetadata().getContributor()) {
+                                addTo(
+                                    reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CONTRIBUTOR),
+                                    split(contributor.getName()),
+                                    contributor.getName()
+                                );
+                            }
+                        }
+                        if (entry.getMetadata().getCreator() != null) {
+                            for (Person creator : entry.getMetadata().getCreator()) {
+                                addTo(
+                                    reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CREATOR),
+                                    split(creator.getName()),
+                                    creator.getName()
+                                );
+                            }
+                        }
+                        if (entry.getMetadata().getProgrammingLanguage() != null) {
+                            for (String language : entry.getMetadata().getProgrammingLanguage()) {
+                                addTo(
+                                    reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES),
+                                    Collections.singletonList(language),
+                                    language
+                                );
+                            }
+                        }
+                    });
+
+                cachedCompletions = reloadedCachedCompletions;
+            } catch (ElasticsearchException | ConnectException ex) {
+                // this may happen mainly during testing, when
+                // test elasticsearch server is not setup correctly
+                LOGGER.error("Cannot update completion Cache", ex);
+            } catch (IOException ex) {
+                // this may happen mainly during testing, when
+                // test elasticsearch server is not setup correctly
+                LOGGER.error("IOException: Cannot update completion Cache");
             }
         }
-        cachedCompletions = reloadedCachedCompletions;
     }
 
     public static String filterDateString(String sourceAsJSON) {
@@ -341,7 +320,8 @@ public class MetaDataRepository {
      */
     public static ObjectMapper getSearchResultObjectMapper() {
         ObjectMapper objectMapper = new ObjectMapper();
-        objectMapper.registerModule(new JavaTimeModule());
+        final JavaTimeModule javaTimeModule = new JavaTimeModule();
+        objectMapper.registerModule(javaTimeModule);
         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         return objectMapper;
     }
@@ -374,72 +354,149 @@ public class MetaDataRepository {
         return result;
     }
 
-    public SearchResultsDTO getAllResources() throws IOException {
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder.size(10000);
-        sourceBuilder.query(QueryBuilders.matchAllQuery());
-        searchRequest.source(sourceBuilder);
-        SearchResponse searchResponse = null;
-        try {
-            searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
-        } catch (IOException e) {
-            LOGGER.warn("Error while searching for all resources", e);
-        }
-        return parseSearchResponse(searchResponse);
+    public List<SearchResultDTO> getAllResources() throws IOException {
+        return getAllResultsAsStream().collect(Collectors.toList());
     }
 
     public SearchResultsDTO searchByEmail(String email, int pageSize, Optional<User> user) throws IOException {
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-
-        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder queryBuilder = new BoolQuery.Builder();
 
-        BoolQueryBuilder authorBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder authorBuilder = new BoolQuery.Builder();
         if (email != null) {
-            authorBuilder.should(
-                QueryBuilders
-                    .multiMatchQuery(
-                        email,
-                        SearchRepositoryConstants.METADATA_CREATOR_EMAIL,
-                        SearchRepositoryConstants.METADATA_CONTRIBUTOR_EMAIL,
-                        SearchRepositoryConstants.METADATA_PUBLISHER_EMAIL
-                    )
-                    .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX)
+            authorBuilder.should(q ->
+                q.multiMatch(q2 ->
+                    new MultiMatchQuery.Builder()
+                        .fields(
+                            SearchRepositoryConstants.METADATA_CREATOR_EMAIL,
+                            SearchRepositoryConstants.METADATA_CONTRIBUTOR_EMAIL,
+                            SearchRepositoryConstants.METADATA_PUBLISHER_EMAIL
+                        )
+                        .type(TextQueryType.PhrasePrefix)
+                        .query(email)
+                )
             );
         }
-        if (authorBuilder.hasClauses()) {
-            queryBuilder.must(authorBuilder);
+
+        queryBuilder.must(q -> q.bool(authorBuilder.build()));
+
+        addAuthorizationQueryWithJavaApi(user, queryBuilder);
+
+        try {
+            final BoolQuery query = queryBuilder.build();
+            // LOGGER.info("ElasticSearch Query using Java Client API:\n{}", query)
+            co.elastic.clients.elasticsearch.core.SearchResponse<SearchResultDTO> searchResponse = elasticsearchAPIClient.search(
+                search -> search.index(SearchRepositoryConstants.INDEX_METADATA).query(q -> q.bool(query)).from(0).size(pageSize),
+                SearchResultDTO.class
+            );
+
+            return parseSearchResponseWithJavaAPI(searchResponse);
+        } catch (ElasticsearchException ese) {
+            LOGGER.warn("Exception in elastic search", ese);
+            throw ese;
+        } catch (ConnectException ce) {
+            final RestClientTransport esTransport = (RestClientTransport) elasticsearchAPIClient._transport();
+            final String nodes = esTransport.restClient().getNodes().stream().map(Object::toString).collect(Collectors.joining(", "));
+            LOGGER.warn("ConnectionException in elastic search: {} ", nodes, ce);
+            throw ce;
         }
+    }
 
-        // Authorization restrictions
-        final BoolQueryBuilder publicQuery = createPublicQuery();
+    public static class StreamedSearchResults {
 
-        if (user.isEmpty()) {
-            queryBuilder.must(publicQuery);
-        } else {
-            final Collection<GrantedAuthority> authorities = user.get().getAuthorities();
-            final BoolQueryBuilder authQuery = QueryBuilders.boolQuery().boost(0.0f);
-            final Stream<QueryBuilder> prefixAuthQueries = authorities
-                .stream()
-                .filter(auth -> !auth.getAuthority().startsWith("ROLE"))
-                .filter(auth -> !auth.getAuthority().startsWith(SCOPE))
-                .map(auth -> QueryBuilders.prefixQuery(SearchRepositoryConstants.PROJECT_NAMESPACE, auth.getAuthority()));
-            prefixAuthQueries.forEach(authQuery::should);
-            if (authQuery.hasClauses()) {
-                queryBuilder.must(QueryBuilders.boolQuery().should(authQuery).should(publicQuery));
-            } else {
-                queryBuilder.must(publicQuery);
-            }
+        private final long estimatedSize;
+        private final Stream<SearchResultDTO> searchStream;
+
+        protected StreamedSearchResults(long estimatedSize, Stream<SearchResultDTO> searchStream) {
+            super();
+            this.estimatedSize = estimatedSize;
+            this.searchStream = searchStream;
+        }
+
+        public long getEstimatedSize() {
+            return estimatedSize;
         }
 
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder.query(queryBuilder).size(pageSize).from(0);
+        public Stream<SearchResultDTO> getSearchStream() {
+            return searchStream;
+        }
+    }
 
-        searchRequest.source(sourceBuilder);
+    /**
+     * Method to stream all exercises.
+     *
+     * @return the search result page DTO
+     * @throws IOException
+     */
+    public Stream<SearchResultDTO> getAllResultsAsStream() throws IOException {
+        final org.codeability.sharing.plugins.api.search.SearchInputMetadataDTO emptySearchMetadata = new org.codeability.sharing.plugins.api.search.SearchInputMetadataDTO(
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+        );
+        SearchInputDTO emptySearchQuery = new SearchInputDTO(null, emptySearchMetadata, null, null, null, 0);
+        return streamResults(emptySearchQuery, Optional.empty(), true).getSearchStream();
+    }
 
-        SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
+    /**
+     * returns the hits from the meta data search index for the input query
+     * parameters.
+     *
+     * @param searchInputDTO          the query parameters.
+     * @param user                    the user that queries.
+     * @param ignoreUserAuthorization if true, user authorization is ignored
+     * @return the search hits.
+     * @throws IOException when search index is not available.
+     */
+    @SuppressWarnings({ "PMD.ExcessiveMethodLength", "PMD.NcssCount" })
+    public StreamedSearchResults streamResults(
+        final SearchInputDTO searchInputDTO,
+        final Optional<User> user,
+        boolean ignoreUserAuthorization
+    ) throws IOException {
+        searchInputDTO.setPageSize(SCROLL_SIZE); // for streaming we set the page size to SCROLL_SIZE
+        searchInputDTO.setPage(0); // we force start page to 0!
+
+        final SearchResultsDTO pageDetails = pageDetailsWithJavaApi(searchInputDTO, user, ignoreUserAuthorization);
+
+        final long size = pageDetails.getHitCount();
+
+        Iterator<SearchResultDTO> resultsIterator = new Iterator<>() {
+            int count = 0;
+            int pagePointer = 0;
+            SearchResultsDTO currentPageDetails = pageDetails;
+
+            @Override
+            public boolean hasNext() {
+                return count < currentPageDetails.getHitCount();
+            }
 
-        return parseSearchResponse(searchResponse);
+            @Override
+            public SearchResultDTO next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                if (pagePointer < SCROLL_SIZE) {
+                    count++;
+                    return currentPageDetails.getSearchResult().get(pagePointer++);
+                } else {
+                    // load next page
+                    searchInputDTO.setPage(searchInputDTO.getPage() + 1);
+                    try {
+                        currentPageDetails = pageDetailsWithJavaApi(searchInputDTO, user, ignoreUserAuthorization);
+                    } catch (IOException e) {
+                        throw new ConcurrentModificationException("It seems that scroll result changed during update", e);
+                    }
+                    pagePointer = 0;
+                    return next();
+                }
+            }
+        };
+
+        var resultStream = StreamSupport.stream(Spliterators.spliterator(resultsIterator, (int) size, Spliterator.SIZED), false);
+        return new StreamedSearchResults(size, resultStream);
     }
 
     /**
@@ -453,60 +510,122 @@ public class MetaDataRepository {
      * @return the search hits.
      * @throws IOException when search index is not available.
      */
-    @SuppressWarnings(
-        { "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength", "PMD.NcssCount" }
-    )
-    public SearchResultsDTO pageDetails(org.codeability.sharing.plugins.api.search.SearchInputDTO searchInputDTO, Optional<User> user)
+    @SuppressWarnings({ "PMD.ExcessiveMethodLength", "PMD.NcssCount" })
+    public SearchResultsDTO pageDetailsWithJavaApi(SearchInputDTO searchInputDTO, Optional<User> user) throws IOException {
+        return pageDetailsWithJavaApi(searchInputDTO, user, false);
+    }
+
+    /**
+     * returns the hits from the meta data search index for the input query
+     * parameters.
+     *
+     * @param searchInputDTO          the query parameters.
+     * @param user                    the user that queries.
+     * @param ignoreUserAuthorization ignores the authorization of the user
+     * @return the search hits.
+     * @throws IOException when search index is not available.
+     */
+    @SuppressWarnings({ "PMD.ExcessiveMethodLength", "PMD.NcssCount" })
+    public SearchResultsDTO pageDetailsWithJavaApi(SearchInputDTO searchInputDTO, Optional<User> user, boolean ignoreUserAuthorization)
         throws IOException {
-        fillAutoCompletion();
+        BoolQuery.Builder queryBuilder = createQueryBuilder(searchInputDTO, user, ignoreUserAuthorization);
+
+        final int pageSize = searchInputDTO.getPageSize();
+        final int from = (searchInputDTO.getPage()) * pageSize;
 
-        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
+        try {
+            final BoolQuery query = queryBuilder.build();
+            // LOGGER.info("ElasticSearch Query using Java Client API:\n{}", query)
+            co.elastic.clients.elasticsearch.core.SearchResponse<SearchResultDTO> searchResponse = elasticsearchAPIClient.search(
+                search -> search.index(SearchRepositoryConstants.INDEX_METADATA).query(q -> q.bool(query)).from(from).size(pageSize),
+                SearchResultDTO.class
+            );
+
+            return parseSearchResponseWithJavaAPI(searchResponse);
+        } catch (ElasticsearchException ese) {
+            LOGGER.warn("Exception in elastic search", ese);
+            throw ese;
+        } catch (ConnectException ce) {
+            final RestClientTransport esTransport = (RestClientTransport) elasticsearchAPIClient._transport();
+            final String nodes = esTransport.restClient().getNodes().stream().map(Object::toString).collect(Collectors.joining(", "));
+            LOGGER.warn("ConnectionException in elastic search: {} ", nodes, ce);
+            throw ce;
+        }
+    }
+
+    /**
+     * creates the query (builder) for the searchInput and the user authorization
+     *
+     * @param searchInputDTO          the search input
+     * @param user                    the user (optional)
+     * @param ignoreUserAuthorization if true, the user authorization is ignored
+     * @return
+     */
+    @SuppressWarnings("PMD.ExcessiveMethodLength")
+    private BoolQuery.Builder createQueryBuilder(SearchInputDTO searchInputDTO, Optional<User> user, boolean ignoreUserAuthorization) {
+        BoolQuery.Builder queryBuilder = new BoolQuery.Builder();
 
-        BoolQueryBuilder fullTextBuilder = QueryBuilders.boolQuery();
         forEachTerm(
             searchInputDTO.getFulltextQuery(),
             term ->
-                fullTextBuilder.should(
-                    QueryBuilders
-                        .multiMatchQuery(
-                            term,
-                            SearchRepositoryConstants.METADATA_DESCRIPTION,
-                            SearchRepositoryConstants.METADATA_KEYWORDS,
-                            SearchRepositoryConstants.METADATA_TITLE
-                        )
-                        .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX)
+                queryBuilder.must(q ->
+                    q.multiMatch(
+                        new MultiMatchQuery.Builder()
+                            .query(term)
+                            .fields(
+                                SearchRepositoryConstants.METADATA_DESCRIPTION,
+                                SearchRepositoryConstants.METADATA_KEYWORDS,
+                                SearchRepositoryConstants.METADATA_TITLE
+                            )
+                            .type(TextQueryType.PhrasePrefix)
+                            .build()
+                    )
                 )
         );
-        if (fullTextBuilder.hasClauses()) {
-            queryBuilder.must(fullTextBuilder);
-        }
 
-        BoolQueryBuilder plBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder plBuilder = new BoolQuery.Builder();
         forEachTerm(
             searchInputDTO.getMetadata().getProgrammingLanguage(),
-            term -> plBuilder.should(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, term))
+            term ->
+                plBuilder.should(q ->
+                    q.prefix(
+                        new PrefixQuery.Builder()
+                            .value(term)
+                            .caseInsensitive(true)
+                            .field(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES)
+                            .build()
+                    )
+                ),
+            () -> queryBuilder.must(q -> q.bool(plBuilder.build()))
         );
-        if (plBuilder.hasClauses()) {
-            queryBuilder.must(plBuilder);
-        }
 
-        BoolQueryBuilder keywordBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder keywordBuilder = new BoolQuery.Builder();
         forEachTerm(
             searchInputDTO.getMetadata().getKeyword(),
-            term -> keywordBuilder.should(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_KEYWORDS, term))
+            term ->
+                keywordBuilder.should(q ->
+                    q.matchPhrasePrefix(
+                        new MatchPhrasePrefixQuery.Builder().query(term).field(SearchRepositoryConstants.METADATA_KEYWORDS).build()
+                    )
+                ),
+            () -> queryBuilder.must(q -> q.bool(keywordBuilder.build()))
         );
-        if (keywordBuilder.hasClauses()) {
-            queryBuilder.must(keywordBuilder);
-        }
 
-        BoolQueryBuilder formatBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder formatBuilder = new BoolQuery.Builder();
         forEachTerm(
             searchInputDTO.getMetadata().getFormat(),
-            term -> formatBuilder.should(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_FORMATS, term))
+            term ->
+                formatBuilder.should(q ->
+                    q.prefix(
+                        new PrefixQuery.Builder()
+                            .value(term)
+                            .field(SearchRepositoryConstants.METADATA_FORMATS)
+                            .caseInsensitive(true)
+                            .build()
+                    )
+                ),
+            () -> queryBuilder.must(q -> q.bool(formatBuilder.build()))
         );
-        if (formatBuilder.hasClauses()) {
-            queryBuilder.must(formatBuilder);
-        }
 
         String parentId = searchInputDTO.getMetadata().getParentId();
         SearchResultDTO parentExercise = null;
@@ -514,7 +633,7 @@ public class MetaDataRepository {
             try {
                 parentExercise = getExerciseById(parentId, user);
             } catch (NotFoundException e) {
-                String stripped = convertToPreviousId(parentId);
+                String stripped = convertToOldFormat(parentId);
                 if (stripped.equals(parentId)) {
                     try {
                         parentExercise = getExerciseById(stripped, user);
@@ -528,73 +647,69 @@ public class MetaDataRepository {
                 return null;
             } else {
                 String[] childrenIds = parentExercise.getFile().getChildren();
-                BoolQueryBuilder collectionBuilder = QueryBuilders.boolQuery();
+                BoolQuery.Builder collectionBuilder = new BoolQuery.Builder();
                 for (String childId : childrenIds) {
-                    collectionBuilder.should(QueryBuilders.termQuery(SearchRepositoryConstants.EXERCISE_ID, childId));
-                    String previousId = convertToPreviousId(childId);
+                    collectionBuilder.should(q ->
+                        q.term((new TermQuery.Builder()).value(childId).field(SearchRepositoryConstants.EXERCISE_ID).build())
+                    );
+                    String previousId = convertToOldFormat(childId);
                     if (!previousId.equals(childId)) {
-                        collectionBuilder.should(QueryBuilders.termQuery(SearchRepositoryConstants.EXERCISE_ID, previousId));
+                        collectionBuilder.should(q ->
+                            q.term((new TermQuery.Builder()).value(previousId).field(SearchRepositoryConstants.EXERCISE_ID).build())
+                        );
                     }
                 }
-                queryBuilder.must(collectionBuilder);
+                queryBuilder.must(q -> q.bool(collectionBuilder.build()));
             }
         }
 
-        BoolQueryBuilder authorBuilder = QueryBuilders.boolQuery();
         if (searchInputDTO.getMetadata().getAuthor() != null) {
-            authorBuilder.should(
-                QueryBuilders
-                    .multiMatchQuery(
-                        searchInputDTO.getMetadata().getAuthor(),
-                        SearchRepositoryConstants.METADATA_CREATOR,
-                        SearchRepositoryConstants.METADATA_CONTRIBUTOR,
-                        SearchRepositoryConstants.METADATA_PUBLISHER
+            queryBuilder.must(q1 ->
+                q1.bool(q2 ->
+                    q2.should(q ->
+                        q.multiMatch(
+                            new MultiMatchQuery.Builder()
+                                .query(searchInputDTO.getMetadata().getAuthor())
+                                .type(TextQueryType.PhrasePrefix)
+                                .fields(
+                                    SearchRepositoryConstants.METADATA_CREATOR,
+                                    SearchRepositoryConstants.METADATA_CONTRIBUTOR,
+                                    SearchRepositoryConstants.METADATA_PUBLISHER
+                                )
+                                .build()
+                        )
                     )
-                    .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX)
+                )
             );
         }
-        if (authorBuilder.hasClauses()) {
-            queryBuilder.must(authorBuilder);
-        }
 
         if (searchInputDTO.getMetadata().getNaturalLanguage() != null && !searchInputDTO.getMetadata().getNaturalLanguage().isEmpty()) {
-            BoolQueryBuilder nlBuilder = QueryBuilders.boolQuery();
+            BoolQuery.Builder nlBuilder = new BoolQuery.Builder();
+
             searchInputDTO
                 .getMetadata()
                 .getNaturalLanguage()
-                .forEach(language -> nlBuilder.should(QueryBuilders.matchQuery(SearchRepositoryConstants.METADATA_LANGUAGE, language)));
-            if (nlBuilder.hasClauses()) {
-                queryBuilder.must(nlBuilder);
-            }
+                .forEach(language ->
+                    nlBuilder.should(q ->
+                        q.match(new MatchQuery.Builder().query(language).field(SearchRepositoryConstants.METADATA_LANGUAGE).build())
+                    )
+                );
+            queryBuilder.must(q -> q.bool((nlBuilder.build())));
         }
 
         forEachTerm(
             searchInputDTO.getMetadata().getLicense(),
-            term -> queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_LICENSE, term))
+            term ->
+                queryBuilder.must(q ->
+                    q.prefix(new PrefixQuery.Builder().value(term).field(SearchRepositoryConstants.METADATA_LICENSE).build())
+                )
         );
 
-        addAuthorizationQuery(user, queryBuilder);
-
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder
-            .query(queryBuilder)
-            .size(searchInputDTO.getPageSize())
-            .from((searchInputDTO.getPage()) * searchInputDTO.getPageSize());
-
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-        searchRequest.source(sourceBuilder);
-
-        SearchResponse searchResponse;
-
-        final Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder().setWarningsHandler(warnings -> false);
-        try {
-            searchResponse = elasticsearchClient.search(searchRequest, optionsBuilder.build());
-        } catch (ElasticsearchException ese) {
-            LOGGER.warn("Exception in elastic search", ese);
-            throw ese;
+        if (!ignoreUserAuthorization) {
+            addAuthorizationQueryWithJavaApi(user, queryBuilder);
         }
 
-        return parseSearchResponse(searchResponse);
+        return queryBuilder;
     }
 
     /**
@@ -604,7 +719,7 @@ public class MetaDataRepository {
      * @param exerciseId
      * @return
      */
-    public String convertToPreviousId(String exerciseId) {
+    public String convertToOldFormat(String exerciseId) {
         Pattern newParser = Pattern.compile("^\\[(\\d+)\\](.*)$");
         Matcher matcher = newParser.matcher(exerciseId.trim());
         if (matcher.matches()) {
@@ -627,61 +742,79 @@ public class MetaDataRepository {
      * @param user
      * @param queryBuilder
      */
-    private void addAuthorizationQuery(Optional<User> user, BoolQueryBuilder queryBuilder) {
+    private void addAuthorizationQueryWithJavaApi(Optional<User> user, BoolQuery.Builder queryBuilder) {
         // Authorization restrictions
-        final BoolQueryBuilder publicQuery = createPublicQuery();
+        final TermQuery.Builder simplePublicQuery = new TermQuery.Builder()
+            .value("public")
+            .field(SearchRepositoryConstants.PROJECT_VISIBILITY)
+            .boost(0.0f);
+        final ExistsQuery.Builder publicVisibilityQuery = new ExistsQuery.Builder()
+            .field(SearchRepositoryConstants.METADATA_PUBLICVISIBILITY);
+        final BoolQuery.Builder publicQuery = new BoolQuery.Builder()
+            .should(q -> q.term(simplePublicQuery.build()))
+            .should(q -> q.exists(publicVisibilityQuery.build()));
 
         if (user.isEmpty()) {
-            queryBuilder.must(publicQuery);
+            queryBuilder.must(q -> q.bool(publicQuery.build()));
         } else {
+            final AtomicBoolean hasAuthQueries = new AtomicBoolean(false);
             final Collection<GrantedAuthority> authorities = user.get().getAuthorities();
-            final BoolQueryBuilder authQuery = QueryBuilders.boolQuery().boost(0.0f);
+            final BoolQuery.Builder authQuery = new BoolQuery.Builder().boost(0.0f);
             // old membership management (can be eliminated some day)
-            final Stream<QueryBuilder> prefixAuthQueries = authorities
+            final Stream<PrefixQuery.Builder> prefixAuthQueries = authorities
                 .stream()
                 .filter(auth -> !Authority.isStandardRole(auth.getAuthority()))
                 .filter(auth -> !auth.getAuthority().startsWith(SCOPE))
-                .map(auth -> QueryBuilders.termQuery(SearchRepositoryConstants.PROJECT_NAMESPACE, auth.getAuthority()));
-            prefixAuthQueries.forEach(pq -> authQuery.should(pq));
+                .map(auth -> new PrefixQuery.Builder().field(SearchRepositoryConstants.PROJECT_NAMESPACE).value(auth.getAuthority()));
+            prefixAuthQueries.forEach(pq -> {
+                hasAuthQueries.set(true);
+                authQuery.should(q -> q.prefix(pq.build()));
+            });
 
             // new membership management
-            final Stream<QueryBuilder> prefixAuthQueries2 = authorities
+            final Stream<TermQuery.Builder> prefixAuthQueries2 = authorities
                 .stream()
                 .filter(auth -> !Authority.isStandardRole(auth.getAuthority()))
                 .filter(auth -> !auth.getAuthority().startsWith(SCOPE))
-                .map(auth -> QueryBuilders.termQuery(SearchRepositoryConstants.PROJECT_GROUPS, auth.getAuthority()));
-            prefixAuthQueries2.forEach(authQuery::should);
+                .map(auth -> new TermQuery.Builder().field(SearchRepositoryConstants.PROJECT_GROUPS).value(auth.getAuthority()));
+            prefixAuthQueries2.forEach(paq -> {
+                hasAuthQueries.set(true);
+                authQuery.should(q -> q.term(paq.build()));
+            });
+
             final String email = user.get().getUsername().toLowerCase(Locale.getDefault());
             if (StringUtils.isNotEmpty(email)) {
-                authQuery.should(QueryBuilders.termQuery(SearchRepositoryConstants.PROJECT_USERS, email));
+                hasAuthQueries.set(true);
+                authQuery.should(q -> q.term(new TermQuery.Builder().field(SearchRepositoryConstants.PROJECT_USERS).value(email).build()));
             }
-            if (authQuery.hasClauses()) {
-                queryBuilder.must(QueryBuilders.boolQuery().should(authQuery).should(publicQuery));
+
+            if (hasAuthQueries.get()) {
+                queryBuilder.must(q1 ->
+                    q1.bool(new BoolQuery.Builder().should(q -> q.bool(authQuery.build())).should(q -> q.bool(publicQuery.build())).build())
+                );
             } else {
-                queryBuilder.must(publicQuery);
+                queryBuilder.must(q -> q.bool(publicQuery.build()));
             }
         }
     }
 
-    private BoolQueryBuilder createPublicQuery() {
-        final TermQueryBuilder simplePublicQuery = QueryBuilders
-            .termQuery(SearchRepositoryConstants.PROJECT_VISIBILITY, "public")
-            .boost(0.0f);
-        final ExistsQueryBuilder publicVisibilityQuery = QueryBuilders.existsQuery(SearchRepositoryConstants.METADATA_PUBLICVISIBILITY);
-
-        return QueryBuilders.boolQuery().should(simplePublicQuery).should(publicVisibilityQuery);
-    }
-
-    private static SearchResultsDTO parseSearchResponse(final SearchResponse searchResponse) throws JsonProcessingException {
-        float maxScore = searchResponse.getHits().getMaxScore();
-        ObjectMapper objectMapper = new ObjectMapper();
-        objectMapper.registerModule(new JavaTimeModule());
-        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    private static SearchResultsDTO parseSearchResponseWithJavaAPI(
+        final co.elastic.clients.elasticsearch.core.SearchResponse<SearchResultDTO> searchResponse
+    ) {
+        final Double maxScoreD = searchResponse.hits().maxScore();
+        final double maxScore = maxScoreD == null ? 0.0d : maxScoreD;
 
         final List<SearchResultDTO> searchResults = new ArrayList<>();
-        for (SearchHit searchHit : searchResponse.getHits().getHits()) {
-            float hitScore = searchHit.getScore();
-            final SearchResultDTO parsedSearchHit = parseSearchHit(searchHit, objectMapper);
+        for (Hit<SearchResultDTO> searchHit : searchResponse.hits().hits()) {
+            final Double scoreD = searchHit.score();
+            double hitScore = scoreD == null ? 0.0 : scoreD;
+            final SearchResultDTO parsedSearchHit = searchHit.source();
+            if (parsedSearchHit == null) {
+                LOGGER.warn("received null search hit?");
+                continue;
+            }
+
+            parsedSearchHit.setExerciseId(searchHit.id());
             int ranking5 = 1;
             if (maxScore > 0.0f) {
                 ranking5 = (int) ((hitScore * 5.0) / maxScore + 0.5f);
@@ -690,10 +823,10 @@ public class MetaDataRepository {
             searchResults.add(parsedSearchHit);
         }
 
-        TotalHits totalHits = searchResponse.getHits().getTotalHits();
+        co.elastic.clients.elasticsearch.core.search.TotalHits totalHits = searchResponse.hits().total();
         long hitCount = 0;
         if (totalHits != null) {
-            hitCount = totalHits.value;
+            hitCount = totalHits.value();
         }
         final SearchResultsDTO results = new SearchResultsDTO();
         results.setHitCount(hitCount);
@@ -701,22 +834,26 @@ public class MetaDataRepository {
         return results;
     }
 
-    private static SearchResultDTO parseSearchHit(SearchHit searchHit, ObjectMapper objectMapper) throws JsonProcessingException {
-        String sourceAsString = filterDateString(searchHit.getSourceAsString());
-        final SearchResultDTO mappedValue = objectMapper.readValue(sourceAsString, SearchResultDTO.class);
-        mappedValue.setExerciseId(searchHit.getId());
-        return mappedValue;
+    /** Helper **/
+    private static void forEachTerm(String searchTerm, Consumer<String> exec) {
+        forEachTerm(searchTerm, exec, () -> {});
     }
 
     /** Helper **/
-    private static void forEachTerm(String searchTerm, Consumer<String> exec) {
+    private static void forEachTerm(String searchTerm, Consumer<String> exec, Runnable whenAtLeastOne) {
         if (searchTerm == null) {
             return;
         }
         StringTokenizer st = new StringTokenizer(searchTerm);
+
+        boolean atLeastOne = false;
         while (st.hasMoreTokens()) {
+            atLeastOne = true;
             exec.accept(st.nextToken());
         }
+        if (atLeastOne) {
+            whenAtLeastOne.run();
+        }
     }
 
     public SearchResultsDTO getExercisesById(Stream<String> exerciseIds, Optional<User> user, int page, int pageSize) {
@@ -736,42 +873,60 @@ public class MetaDataRepository {
      * @return
      */
     public SearchResultsDTO getExercisesById(Stream<String> exerciseIds, Optional<User> user, int page, int pageSize, Instant since) {
-        BoolQueryBuilder idQueryBuilder = QueryBuilders.boolQuery();
-        exerciseIds.forEach(exerciseId -> idQueryBuilder.should(QueryBuilders.matchQuery(SearchRepositoryConstants.EXERCISE_ID, exerciseId))
-        );
+        BoolQuery.Builder idQueryBuilder = new BoolQuery.Builder();
+
+        AtomicInteger idCounter = new AtomicInteger(0);
+        exerciseIds.forEach(exerciseId -> {
+            idCounter.incrementAndGet();
+            idQueryBuilder.should(q ->
+                q.match(q2 -> new MatchQuery.Builder().field(SearchRepositoryConstants.EXERCISE_ID).query(exerciseId))
+            );
+        });
+
         // Böse Falle: Wenn exerciseIds empty, dann wird alles zurückgegeben!
-        if (!idQueryBuilder.hasClauses()) {
+        if (idCounter.get() == 0) {
             final SearchResultsDTO results = new SearchResultsDTO();
             results.setHitCount(0L);
             results.setSearchResult(new ArrayList<>());
             return results;
         }
-        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder queryBuilder = new BoolQuery.Builder();
+
+        queryBuilder.must(q -> q.bool(idQueryBuilder.build()));
 
-        queryBuilder.must(idQueryBuilder);
         if (since != null) {
-            queryBuilder.must(
-                QueryBuilders.rangeQuery(SearchRepositoryConstants.PROJECT_LASTACTIVITYAT).gte(since)
-                /* .lte(Instant.now()) */
+            // Noch ein böse Falle! JsonData.of(since) konvertiert nicht in Zeitstring
+            // sondern in
+            // ein Array of Strings
+            String sinceString = DateTimeFormatter.ISO_INSTANT.format(since);
+            // "2023-03-15T08:32:52
+            queryBuilder.must(q ->
+                q.range(
+                    new RangeQuery.Builder().field(SearchRepositoryConstants.PROJECT_LASTACTIVITYAT).gte(JsonData.of(sinceString)).build()
+                )
             );
         }
 
-        addAuthorizationQuery(user, queryBuilder);
-
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder.query(queryBuilder).size(pageSize).from(page * pageSize);
-
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-        searchRequest.source(sourceBuilder);
+        addAuthorizationQueryWithJavaApi(user, queryBuilder);
 
         try {
-            SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
+            final BoolQuery query = queryBuilder.build();
+            LOGGER.info("ElasticSearch Query using Java Client API:\n{}", query);
+            co.elastic.clients.elasticsearch.core.SearchResponse<SearchResultDTO> searchResponse = elasticsearchAPIClient.search(
+                search ->
+                    search.index(SearchRepositoryConstants.INDEX_METADATA).query(q -> q.bool(query)).from(page * pageSize).size(pageSize),
+                SearchResultDTO.class
+            );
 
-            return parseSearchResponse(searchResponse);
-        } catch (JsonProcessingException e) {
-            throw new NotFoundException("Unable to process JSON, Exercise with id " + idQueryBuilder.toString() + " not found", e);
-        } catch (IOException e) {
-            throw new NotFoundException("IOException, Query for exercise with id " + idQueryBuilder.toString() + " went wrong", e);
+            return parseSearchResponseWithJavaAPI(searchResponse);
+        } catch (ElasticsearchException ese) {
+            LOGGER.warn("Exception in elastic search", ese);
+            throw ese;
+        } catch (IOException ce) {
+            final RestClientTransport esTransport = (RestClientTransport) elasticsearchAPIClient._transport();
+            final String nodes = esTransport.restClient().getNodes().stream().map(Object::toString).collect(Collectors.joining(", "));
+            LOGGER.warn("ConnectionException in elastic search: {} ", nodes, ce);
+            throw new ElasticsearchException("IOException", ce);
         }
     }
 
@@ -800,8 +955,10 @@ public class MetaDataRepository {
     public TreeNode<SearchResultDTO> getExerciseCollectionTree(ExerciseId exerciseId, Optional<User> user) {
         SearchResultDTO root = getExerciseById(exerciseId, user);
         if (root == null) {
+            LOGGER.debug("Exercise with id {} not found", exerciseId);
             return null;
         }
+        LOGGER.debug("Loaded Exercise with id {}: ({})", exerciseId, root);
         TreeNode<SearchResultDTO> result = new TreeNode<>(root);
         final String[] children = root.getFile().getChildren();
         if (children != null) {
@@ -838,42 +995,45 @@ public class MetaDataRepository {
         if (!bracketedExerciseId.startsWith("[")) { // just for backward compatibility
             bracketedExerciseId = "[" + exerciseId + "]";
         }
-        SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
+        final String finalBracketedExerciseId = bracketedExerciseId;
 
-        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
+        BoolQuery.Builder queryBuilder = new BoolQuery.Builder();
 
-        queryBuilder.must(QueryBuilders.matchQuery(SearchRepositoryConstants.EXERCISE_ID, bracketedExerciseId));
-
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder.query(queryBuilder).size(10);
-        addAuthorizationQuery(user, queryBuilder);
+        queryBuilder.must(q ->
+            q.match(q2 -> new MatchQuery.Builder().field(SearchRepositoryConstants.EXERCISE_ID).query(finalBracketedExerciseId))
+        );
 
-        searchRequest.source(sourceBuilder);
+        addAuthorizationQueryWithJavaApi(user, queryBuilder);
 
         try {
-            SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
-
-            ObjectMapper objectMapper = new ObjectMapper();
-            objectMapper.registerModule(new JavaTimeModule());
-            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+            final BoolQuery query = queryBuilder.build();
+            // LOGGER.info("ElasticSearch Query using Java Client API:\n{}", query)
+            co.elastic.clients.elasticsearch.core.SearchResponse<SearchResultDTO> searchResponse = elasticsearchAPIClient.search(
+                search -> search.index(SearchRepositoryConstants.INDEX_METADATA).query(q -> q.bool(query)).from(0).size(1),
+                SearchResultDTO.class
+            );
 
-            TotalHits totalHits = searchResponse.getHits().getTotalHits();
+            co.elastic.clients.elasticsearch.core.search.TotalHits totalHits = searchResponse.hits().total();
             long hitCount = 0;
             if (totalHits != null) {
-                hitCount = totalHits.value;
+                hitCount = totalHits.value();
             }
             if (hitCount > 1) {
                 LOGGER.warn("Found {} hits for exercise id {}, expected only one!", hitCount, exerciseId);
             }
-            if (searchResponse.getHits().getHits().length > 0) {
-                final SearchResultDTO parseSearchHit = parseSearchHit(searchResponse.getHits().getHits()[0], objectMapper);
-                parseSearchHit.setRanking5(5);
-                return parseSearchHit;
+
+            if (hitCount > 0) {
+                final SearchResultsDTO parsedHits = parseSearchResponseWithJavaAPI(searchResponse);
+                return parsedHits.getSearchResult().get(0);
             }
-        } catch (JsonProcessingException e) {
-            throw new NotFoundException("Failed to process Json when searching for exercise with id " + exerciseId, e);
-        } catch (IOException e) {
-            throw new NotFoundException("Encountered IOException when searching for exercise with id " + exerciseId, e);
+        } catch (ElasticsearchException ese) {
+            LOGGER.warn("Exception in elastic search", ese);
+            throw ese;
+        } catch (IOException ce) {
+            final RestClientTransport esTransport = (RestClientTransport) elasticsearchAPIClient._transport();
+            final String nodes = esTransport.restClient().getNodes().stream().map(Object::toString).collect(Collectors.joining(", "));
+            LOGGER.warn("ConnectionException in elastic search: {} ", nodes, ce);
+            throw new NotFoundException("Exercise with id " + exerciseId + " not found", ce);
         }
         throw new NotFoundException("Exercise with id " + exerciseId + " not found");
     }
diff --git a/src/main/java/at/ac/uibk/gitsearch/security/oauth2/UserDetailsFetcher.java b/src/main/java/at/ac/uibk/gitsearch/security/oauth2/UserDetailsFetcher.java
index 90e0ec7f026f99a207e94a16c9e2a21be4fdde24..d6e3013014f949d6081a834209b2d0cd2ccc4255 100644
--- a/src/main/java/at/ac/uibk/gitsearch/security/oauth2/UserDetailsFetcher.java
+++ b/src/main/java/at/ac/uibk/gitsearch/security/oauth2/UserDetailsFetcher.java
@@ -11,6 +11,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.gitlab4j.api.Constants.TokenType;
@@ -94,13 +95,23 @@ public class UserDetailsFetcher {
 
             String email = (String) oidcUser.getAttribute("email");
             User gitUser = gitLabApi.getUserApi().getUserByEmail(email);
-            userToken = requestGitLabUserToken(gitUser, gitLabApi);
 
             if (gitUser == null) {
-                u.setLastModifiedDate(Instant.now());
-                modified = true;
-                return new UserDetailsInfo(userToken, modified);
+                // try to create the user
+                gitUser = createNewGitLabUserFor(oidcUser);
+                String generatedPassword = generatePassword();
+                gitLabApi.getUserApi().createUser(gitUser, generatedPassword, true);
+                gitUser = gitLabApi.getUserApi().getUserByEmail(email);
+
+                if (gitUser == null) {
+                    // gitUser still null. Something went wrong?
+                    u.setLastModifiedDate(Instant.now());
+                    modified = true;
+                    return new UserDetailsInfo(userToken, modified);
+                }
             }
+            userToken = requestGitLabUserToken(gitUser, gitLabApi);
+
             List<Membership> memberships = gitLabApi.getUserApi().getMemberships(gitUser.getId());
             for (Membership membership : memberships) {
                 authorities.add(membership.getSourceName());
@@ -125,6 +136,24 @@ public class UserDetailsFetcher {
         return new UserDetailsInfo(userToken, modified);
     }
 
+    private String generatePassword() {
+        return RandomStringUtils.random(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=%$-.");
+    }
+
+    private User createNewGitLabUserFor(final OidcUser oidcUser) {
+        User gitUser = new User();
+        String email = oidcUser.getEmail();
+        gitUser.setEmail(email);
+        final Object nameAttribute = oidcUser.getAttribute("name");
+        String name = nameAttribute == null ? "unknown" : nameAttribute.toString();
+        gitUser.setName(name);
+
+        String userName = email.replace('@', '_');
+
+        gitUser.setUsername(userName);
+        return gitUser;
+    }
+
     Optional<ImpersonationToken> requestGitLabUserToken(final User gitUser, final GitLabApi gitLabApi) throws GitLabApiException {
         Date expiresAt = new Date(System.currentTimeMillis() + tokenValidityInSeconds * 1000L);
 
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/LikesService.java b/src/main/java/at/ac/uibk/gitsearch/service/LikesService.java
index 3ecad0503c849f88d68369fd9db15cb83cf65508..bc6eb8fb99a3fa16f15432bef234940445745679 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/LikesService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/LikesService.java
@@ -42,10 +42,13 @@ public class LikesService {
      */
     @Transactional(readOnly = true)
     public List<Likes> findAll() {
-        log.debug("Request to get all Likes");
         return likesRepository.findAll();
     }
 
+    public long countAll() {
+        return likesRepository.count();
+    }
+
     /**
      * Get one likes by id.
      *
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/MonitoringService.java b/src/main/java/at/ac/uibk/gitsearch/service/MonitoringService.java
index 14178ecf987d0a775156e2117b266f4c860f2d05..9f32aa99f9e05580e800854d8ce3788f7895a5a8 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/MonitoringService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/MonitoringService.java
@@ -1,29 +1,24 @@
 package at.ac.uibk.gitsearch.service;
 
 import at.ac.uibk.gitsearch.service.dto.StatisticsDTO;
+import co.elastic.clients.elasticsearch._types.ElasticsearchException;
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.Metrics;
 import io.micrometer.core.instrument.Tag;
 import io.micrometer.core.instrument.Tags;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
+import javax.annotation.PostConstruct;
 import org.codeability.sharing.plugins.api.search.SearchResultDTO;
-import org.codeability.sharing.plugins.api.search.SearchResultsDTO;
-import org.codeability.sharing.plugins.api.search.UserProvidedMetadataDTO;
-import org.elasticsearch.ElasticsearchException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 /**
  * Service class for monitoring
@@ -31,44 +26,42 @@ import org.springframework.transaction.annotation.Transactional;
  * @author Johannes Kainz
  */
 @Service
-@Transactional
+// @Transactional // enabling this may cause NullPointerException, because of wrongly applied EnhancerBySpringCGLIB
 public class MonitoringService {
 
+    private static final String UNCLASSIFIED = "Unclassified";
     private static final String TAG_INTERVAL = "interval";
     private static final String TAG_HOURLY_INTERVAL = "hourly";
 
-    private static final String USER_TAG = "classification";
-    private static final String LANGUAGE_TAG = "language";
+    public static final String USER_TAG = "classification";
+    public static final String LANGUAGE_TAG = "language";
 
-    private static final String NUMBER_USER_NAME = "number.users";
-    private static final String NUMBER_EXERCISE_NAME = "number.exercises";
-    private static final String NUMBER_LIKE_NAME = "number.likes";
-    private static final String NUMBER_VIEW_NAME = "number.views";
-    private static final String NUMBER_DOWNLOAD_NAME = "number.downloads";
+    public static final String NUMBER_USER_NAME = "number.users";
+    public static final String NUMBER_EXERCISE_NAME = "number.exercises";
+    public static final String NUMBER_LIKE_NAME = "number.likes";
+    public static final String NUMBER_VIEW_NAME = "number.views";
+    public static final String NUMBER_DOWNLOAD_NAME = "number.downloads";
 
-    private static final Tag HOURLY_TAG = Tag.of(TAG_INTERVAL, TAG_HOURLY_INTERVAL);
+    public static final Tag HOURLY_TAG = Tag.of(TAG_INTERVAL, TAG_HOURLY_INTERVAL);
 
     private final Logger log = LoggerFactory.getLogger(MonitoringService.class);
 
     private final UserService userService;
     private final SearchService searchService;
     private final LikesService likesService;
-
-    private final AtomicLong numberUsers;
-    private final AtomicLong numberActiveUsers;
-
-    private final AtomicLong numberLikes;
-    private final AtomicLong numberViews;
-    private final AtomicLong numberDownloads;
-    private final Map<String, AtomicInteger> numberExercises;
-
-    private final Map<String, AtomicLong> numberLikesMap;
-    private final Map<String, AtomicLong> numberViewsMap;
-    private final Map<String, AtomicLong> numberDownloadsMap;
+    private final StatisticsService statisticsService;
 
     private final MeterRegistry meterRegistry;
 
-    private final StatisticsService statisticsService;
+    // the following elements must be stored explizit, to make sure
+    // that they are not garbage collected
+    private final AtomicInteger allUsers = new AtomicInteger(0);
+    private final AtomicInteger activeUsers = new AtomicInteger(0);
+    private final AtomicInteger allLikes = new AtomicInteger(0);
+    private final AtomicInteger allViews = new AtomicInteger(0);
+    private final AtomicInteger allDownloads = new AtomicInteger(0);
+    private final AtomicInteger allExercises = new AtomicInteger(0);
+    private final Map<String, AtomicInteger> allLanguageCount = new ConcurrentHashMap<>();
 
     @SuppressWarnings("PMD.AvoidDuplicateLiterals")
     public MonitoringService(
@@ -79,252 +72,115 @@ public class MonitoringService {
         StatisticsService statisticsService
     ) {
         this.meterRegistry = meterRegistry;
-
         this.userService = userService;
         this.searchService = searchService;
         this.likesService = likesService;
         this.statisticsService = statisticsService;
+    }
 
-        this.numberExercises = new HashMap<>();
-        this.numberLikesMap = new HashMap<>();
-        this.numberViewsMap = new HashMap<>();
-        this.numberDownloadsMap = new HashMap<>();
-
-        this.numberUsers =
-            meterRegistry.gauge(
-                NUMBER_USER_NAME,
-                Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "All")),
-                new AtomicLong(this.userService.countAllUsers())
-            );
-        this.numberActiveUsers =
-            meterRegistry.gauge(
-                NUMBER_USER_NAME,
-                Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "Active")),
-                new AtomicLong(this.userService.countAllActiveUsers())
-            );
+    @PostConstruct
+    public void initMonitoring() {
+        initializeGauges();
 
-        try {
-            final SearchResultsDTO searchResults = this.getAllSearchResults();
-            final List<String> exerciseIDs = this.getExerciseIDs(searchResults.getSearchResult());
+        monitorHourlyMetrics();
+    }
 
-            this.setNumberExercises(exerciseIDs.size());
-            this.setExercisesLanguageCount(searchResults);
-            this.setExerciseStatistics(exerciseIDs);
-        } catch (IOException | ElasticsearchException exception) {
-            log.warn("An IOException occurred when searching for all exercises\n" + Arrays.toString(exception.getStackTrace()));
-        }
-        this.numberLikes =
-            meterRegistry.gauge(
-                NUMBER_LIKE_NAME,
-                Tags.of(HOURLY_TAG),
-                new AtomicLong(this.numberLikesMap.values().stream().mapToLong(AtomicLong::get).sum())
-            );
-        this.numberViews =
-            meterRegistry.gauge(
-                NUMBER_VIEW_NAME,
-                Tags.of(HOURLY_TAG),
-                new AtomicLong(this.numberViewsMap.values().stream().mapToLong(AtomicLong::get).sum())
-            );
-        this.numberDownloads =
-            meterRegistry.gauge(
-                NUMBER_DOWNLOAD_NAME,
-                Tags.of(HOURLY_TAG),
-                new AtomicLong(this.numberDownloadsMap.values().stream().mapToLong(AtomicLong::get).sum())
-            );
+    private void initializeGauges() {
+        meterRegistry.gauge(NUMBER_USER_NAME, Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "All")), allUsers);
+        meterRegistry.gauge(NUMBER_USER_NAME, Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "Active")), activeUsers);
+        meterRegistry.gauge(NUMBER_LIKE_NAME, Tags.of(HOURLY_TAG), allLikes);
+        meterRegistry.gauge(NUMBER_VIEW_NAME, Tags.of(HOURLY_TAG), allViews);
+        meterRegistry.gauge(NUMBER_DOWNLOAD_NAME, Tags.of(HOURLY_TAG), allDownloads);
+        meterRegistry.gauge(NUMBER_EXERCISE_NAME, Tags.of(HOURLY_TAG, Tag.of(LANGUAGE_TAG, "All")), allExercises);
     }
 
     /**
      * Monitors metrics with an hourly cron job
      */
     @Scheduled(cron = "0 30 * * * ?")
-    public void monitorHourlyMetrics() {
+    public final void monitorHourlyMetrics() {
         log.debug("Started monitoring cron job");
 
-        this.setNumberUsers();
-
-        try {
-            SearchResultsDTO searchResults = this.getAllSearchResults();
-            List<String> exerciseIDs = this.getExerciseIDs(searchResults.getSearchResult());
-
-            this.setNumberExercises(exerciseIDs.size());
-            this.setExercisesLanguageCount(searchResults);
-            this.setExerciseStatistics(exerciseIDs);
-        } catch (IOException exception) {
-            log.warn("An IOException occurred\n" + Arrays.toString(exception.getStackTrace()));
-        }
-        this.setExerciseStatistics();
-
-        log.debug("Finished monitoring cron job");
-    }
-
-    private void setNumberUsers() {
-        log.debug("set the number of users");
-
-        this.numberUsers.set(this.userService.countAllUsers());
-        this.numberActiveUsers.set(this.userService.countAllActiveUsers());
-    }
-
-    /**
-     * Sets likes, views and downloads for each given exerciseID
-     *
-     * @param exerciseIDs given exercise IDs
-     */
-    private void setExerciseStatistics(List<String> exerciseIDs) {
-        log.debug("set the exercise statistics for {}", exerciseIDs);
-
-        exerciseIDs.forEach(exerciseID -> {
-            this.setNumberLikes(exerciseID);
-
-            final Optional<StatisticsDTO> statisticOptional = this.statisticsService.findOneByExerciseID(exerciseID);
-            if (statisticOptional.isPresent()) {
-                StatisticsDTO statistic = statisticOptional.get();
-                this.setNumberViews(exerciseID, statistic.getViews());
-                this.setNumberDownloads(exerciseID, statistic.getDownloads());
+        allUsers.set((int) this.userService.countAllUsers());
+        activeUsers.set((int) this.userService.countAllActiveUsers());
+        allLikes.set((int) likesService.countAll());
+        final List<Integer[]> numberOfDownloadsAndViews = statisticsService.findNumberOfDownloadsAndViews();
+        if (numberOfDownloadsAndViews != null && numberOfDownloadsAndViews.size() == 1 && numberOfDownloadsAndViews.get(0).length == 2) {
+            if (numberOfDownloadsAndViews.get(0)[1] != null) {
+                allViews.set(numberOfDownloadsAndViews.get(0)[1]);
             }
-        });
-    }
 
-    /**
-     * Sets likes, views and downloads
-     *
-     */
-    private void setExerciseStatistics() {
-        log.debug("set the exercise statistics");
-        this.numberLikes.set(this.numberLikesMap.values().stream().mapToLong(AtomicLong::get).sum());
-        this.numberViews.set(this.numberViewsMap.values().stream().mapToLong(AtomicLong::get).sum());
-        this.numberDownloads.set(this.numberDownloadsMap.values().stream().mapToLong(AtomicLong::get).sum());
-    }
-
-    /**
-     * Sets the given value as number of all exercises
-     *
-     * @param value given value
-     */
-    private void setNumberExercises(int value) {
-        if (this.numberExercises.containsKey("All")) {
-            this.numberExercises.get("All").set(value);
-        } else {
-            this.numberExercises.put(
-                    "All",
-                    meterRegistry.gauge(NUMBER_EXERCISE_NAME, Tags.of(HOURLY_TAG, Tag.of(LANGUAGE_TAG, "All")), new AtomicInteger(value))
-                );
-        }
-    }
-
-    /**
-     * Sets the per programming language exercise counters for given search results
-     *
-     * @param searchResults the search results
-     */
-    private void setExercisesLanguageCount(SearchResultsDTO searchResults) {
-        log.debug("set exercise language count");
-
-        Map<String, Integer> languageCount = this.getLanguageCount(searchResults);
-        for (String language : languageCount.keySet()) {
-            if (this.numberExercises.containsKey(language)) {
-                this.numberExercises.get(language).set(languageCount.get(language));
-            } else {
-                this.numberExercises.put(
-                        language,
-                        Metrics.gauge(
-                            NUMBER_EXERCISE_NAME,
-                            Tags.of(HOURLY_TAG, Tag.of(LANGUAGE_TAG, language)),
-                            new AtomicInteger(languageCount.get(language))
-                        )
-                    );
+            if (numberOfDownloadsAndViews.get(0)[0] != null) {
+                allDownloads.set(numberOfDownloadsAndViews.get(0)[0]);
             }
         }
-    }
 
-    /**
-     * Gets a map containing the programming language counts for given search results
-     *
-     * @param searchResults the search results
-     * @return a map containing the language counts
-     */
-    private Map<String, Integer> getLanguageCount(SearchResultsDTO searchResults) {
-        Map<String, Integer> languageCount = new ConcurrentHashMap<>();
-        List<UserProvidedMetadataDTO> metaDataDTO = new ArrayList<>();
-
-        List<SearchResultDTO> searchResultList = searchResults.getSearchResult();
+        AtomicInteger exerciseCounter = new AtomicInteger(0);
+        try {
+            Map<String, Integer> languageCount = new ConcurrentHashMap<>();
+            final Stream<SearchResultDTO> searchResults = this.getAllSearchResults();
 
-        searchResultList.forEach(searchResult -> metaDataDTO.add(searchResult.getMetadata()));
+            searchResults.forEach(exercise -> {
+                exerciseCounter.addAndGet(1);
+                updateLanguageCount(exercise, languageCount);
+            });
 
-        for (UserProvidedMetadataDTO metaData : metaDataDTO) {
-            String[] languageMetaData = metaData.getProgrammingLanguage();
-            if (languageMetaData == null) {
-                languageCount.put("Unclassified", languageCount.getOrDefault("Unclassified", 0) + 1);
-            } else {
-                Arrays
-                    .stream(metaData.getProgrammingLanguage())
-                    .forEach(language -> languageCount.put(language, languageCount.getOrDefault(language, 0) + 1));
-            }
+            this.setExercisesLanguageCount(languageCount);
+        } catch (IOException | ElasticsearchException exception) {
+            log.warn("An IOException occurred\n{}", Arrays.toString(exception.getStackTrace()));
+            return;
+        } catch (org.elasticsearch.ElasticsearchException exception) {
+            // May become deprecated soon
+            log.warn("An org.elasticsearch.ElasticsearchException occurred\n{}", Arrays.toString(exception.getStackTrace()));
+            return;
         }
 
-        return languageCount;
-    }
-
-    /**
-     * Sets the number of likes for a given exercise ID
-     *
-     * @param exerciseID given exercise ID
-     */
-    private void setNumberLikes(String exerciseID) {
-        long numberLikes = this.likesService.findNumberOfLikesByExerciseID(exerciseID);
+        allExercises.set(exerciseCounter.get());
 
-        if (this.numberLikesMap.containsKey(exerciseID)) {
-            this.numberLikesMap.get(exerciseID).set(numberLikes);
-        } else {
-            this.numberLikesMap.put(
-                    exerciseID,
-                    //meterRegistry.gauge(
-                    //    NUMBER_LIKE_NAME,
-                    //    Tags.of(HOURLY_TAG, Tag.of(EXERCISE_TAG, exerciseID)),
-                    new AtomicLong(numberLikes)
-                    //)
-                );
-        }
+        log.debug("Finished monitoring cron job");
     }
 
-    /**
-     * Sets the given number of views for a given exercise ID
-     *
-     *  @param exerciseID given exercise ID
-     * @param views given number of views
-     */
-    private void setNumberViews(String exerciseID, long views) {
-        if (this.numberViewsMap.containsKey(exerciseID)) {
-            this.numberViewsMap.get(exerciseID).set(views);
+    private void updateLanguageCount(SearchResultDTO exercise, Map<String, Integer> languageCount) {
+        var metaData = exercise.getMetadata();
+        String[] languageMetaData = metaData.getProgrammingLanguage();
+        if (languageMetaData == null) {
+            languageCount.put(UNCLASSIFIED, languageCount.getOrDefault(UNCLASSIFIED, 0) + 1);
         } else {
-            this.numberViewsMap.put(
-                    exerciseID,
-                    //meterRegistry.gauge(
-                    //    NUMBER_VIEW_NAME,
-                    //    Tags.of(HOURLY_TAG, Tag.of(EXERCISE_TAG, exerciseID)),
-                    new AtomicLong(views)
-                    //)
-                );
+            Arrays
+                .stream(metaData.getProgrammingLanguage())
+                .forEach(language -> languageCount.put(language, languageCount.getOrDefault(language, 0) + 1));
         }
     }
 
     /**
-     * Sets the  given number of downloads for a given exercise ID
+     * Sets the per programming language exercise counters for given search results
      *
-     *  @param exerciseID given exercise ID
-     * @param downloads given number of downloads
+     * @param searchResults the search results
      */
-    private void setNumberDownloads(String exerciseID, long downloads) {
-        if (this.numberDownloadsMap.containsKey(exerciseID)) {
-            this.numberDownloadsMap.get(exerciseID).set(downloads);
-        } else {
-            this.numberDownloadsMap.put(
-                    exerciseID,
-                    //meterRegistry.gauge(
-                    //    NUMBER_DOWNLOAD_NAME,
-                    //    Tags.of(HOURLY_TAG, Tag.of(EXERCISE_TAG, exerciseID)),
-                    new AtomicLong(downloads)
-                    //)
-                );
+    private void setExercisesLanguageCount(Map<String, Integer> languageCount) {
+        synchronized (this) {
+            languageCount
+                .entrySet()
+                .stream()
+                .forEach(entry -> {
+                    final AtomicInteger existingEntry = allLanguageCount.get(entry.getKey());
+                    if (existingEntry == null) {
+                        final AtomicInteger newEntry = new AtomicInteger(entry.getValue());
+                        Metrics.gauge(NUMBER_EXERCISE_NAME, Tags.of(HOURLY_TAG, Tag.of(LANGUAGE_TAG, entry.getKey())), newEntry);
+                        allLanguageCount.put(entry.getKey(), newEntry);
+                    } else {
+                        existingEntry.set(entry.getValue());
+                    }
+                });
+            // reset outdated languages to zero
+            allLanguageCount
+                .entrySet()
+                .stream()
+                .forEach(entry -> {
+                    if (!languageCount.containsKey(entry.getKey())) {
+                        entry.getValue().set(0);
+                    }
+                });
         }
     }
 
@@ -334,22 +190,7 @@ public class MonitoringService {
      * @return the search results DTO
      * @throws IOException exception for timeout etc.
      */
-    private SearchResultsDTO getAllSearchResults() throws IOException {
-        return this.searchService.searchAllPublicResults();
-    }
-
-    /**
-     * Gets the exercise ids from a search result list
-     *
-     * @param searchResultList the search result list
-     * @return a list of exerciseIDs
-     */
-    private List<String> getExerciseIDs(List<SearchResultDTO> searchResultList) {
-        log.debug("Get the exercise IDs from a list of search results");
-
-        List<String> exerciseIDs = new ArrayList<>();
-
-        searchResultList.forEach(searchResult -> exerciseIDs.add(searchResult.getExerciseId()));
-        return exerciseIDs;
+    private Stream<SearchResultDTO> getAllSearchResults() throws IOException {
+        return this.searchService.getAllResultsAsStream();
     }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/PluginManagementService.java b/src/main/java/at/ac/uibk/gitsearch/service/PluginManagementService.java
index 7075b76db6c7331bc14f5d5faf7a81bc7f7a3241..d247e081c630038502e6e0ab8d1b351e58ba0669 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/PluginManagementService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/PluginManagementService.java
@@ -97,7 +97,7 @@ public class PluginManagementService {
                     log.warn("Cannot connect to connector at {}: {}", registeredConnector.getUrl(), ce.getMessage());
                     configFailures.put(registeredConnector, ce);
                 } catch (Exception e) {
-                    log.warn("Cannot (re-)load connector at {}", registeredConnector, e.getMessage());
+                    log.warn("Cannot (re-)load connector at {}. Caused by {}.", registeredConnector, e.getMessage());
                     configFailures.put(registeredConnector, e);
                     final Iterator<Entry<String, ConnectorConfigWrapper>> pluginIterator = registeredPluginConfigs.entrySet().iterator();
                     while (pluginIterator.hasNext()) {
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/ReviewService.java b/src/main/java/at/ac/uibk/gitsearch/service/ReviewService.java
index 0e5f124646b33cede77fd83866bf3cde3e7188c6..8388cb06d33180ee5539cc848a553c21bed0e537 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/ReviewService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/ReviewService.java
@@ -235,7 +235,7 @@ public class ReviewService {
         mailService.sendEmail(
             userService.getUserById(review.getRequestedBy()).get().getEmail(),
             "New feedback for resource",
-            "A reviewer has just updated you review request for resource: " + review.getResource(),
+            "A reviewer has just updated your review request for resource: " + review.getResource(),
             false,
             false
         );
@@ -250,6 +250,8 @@ public class ReviewService {
             statisticsService.save(statistics);
         }
         notifyReviewers(review.getId(), "The review for " + review.getResource() + " has been deleted.");
+        notifyRequester(review.getId(), "The review for " + review.getResource() + " has been deleted.");
+
         reviewRatingRepository.deleteByReviewId(id);
         reviewRepository.deleteById(id);
     }
@@ -279,6 +281,10 @@ public class ReviewService {
             review.getId(),
             "The review for " + review.getResource() + " has been completed and a badge is now being displayed when people search for it."
         );
+        notifyRequester(
+            review.getId(),
+            "The review for " + review.getResource() + " has been completed and a badge is now being displayed when people search for it."
+        );
     }
 
     public void requestReviewForExercise(ReviewRequest request) {
@@ -323,4 +329,11 @@ public class ReviewService {
             .collect(Collectors.toList());
         emails.stream().forEach(email -> mailService.sendEmail(email, "Review Updated", message, false, false));
     }
+
+    // notify requester of review
+    public void notifyRequester(Long id, String message) {
+        Review review = reviewRepository.findById(id).get();
+        User user = userService.getUserById(review.getRequestedBy()).get();
+        mailService.sendEmail(user.getEmail(), "Review Updated", message, false, false);
+    }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
index 4a66a7157e7c21f7e8a5e4c052c56e2685d8d1f6..115a2adddf6be1ad5ff40c427aa28e05f328c3f0 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
@@ -12,6 +12,7 @@ import java.net.URISyntaxException;
 import java.text.ParseException;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -75,7 +76,7 @@ public class SearchService {
         final Optional<User> principal = tokenProvider.getCurrentPrincipal();
         log.debug("Searchrequest for {} ", searchInput);
 
-        final SearchResultsDTO pageDetails = metaDataRepository.pageDetails(searchInput, principal);
+        final SearchResultsDTO pageDetails = metaDataRepository.pageDetailsWithJavaApi(searchInput, principal);
 
         pageDetails
             .getSearchResult()
@@ -134,7 +135,7 @@ public class SearchService {
     }
 
     // Returns all resources indexed by elastic search
-    public SearchResultsDTO getAllResources() throws IOException {
+    public List<SearchResultDTO> getAllResources() throws IOException {
         return metaDataRepository.getAllResources();
     }
 
@@ -254,8 +255,9 @@ public class SearchService {
         if (tokenProvider.getCurrentPrincipal().isEmpty()) {
             log.warn("Cannot find a principal for for exercise {}", exerciseId);
             return false;
-        }
-        if (tokenProvider.getCurrentPrincipal().get().getAuthorities().contains(AuthoritiesConstantEnum.ADMIN.getGrantedAuthority())) {
+        } else if (
+            tokenProvider.getCurrentPrincipal().get().getAuthorities().contains(AuthoritiesConstantEnum.ADMIN.getGrantedAuthority())
+        ) {
             log.info("Admin can access everything : {}", exerciseId);
             return true; // ADMIN is always allowed
         }
@@ -320,19 +322,50 @@ public class SearchService {
         return result;
     }
 
+    /**
+     * Method to stream all exercises.
+     *
+     * @return the search result page DTO
+     * @throws IOException
+     */
+    public Stream<SearchResultDTO> getAllResultsAsStream() throws IOException {
+        return metaDataRepository.getAllResultsAsStream();
+    }
+
+    /**
+     * Method to search for all public exercises.
+     * Searches all public exercises with an empty search query and without a
+     * specified user.
+     *
+     * @return the search result page DTO
+     * @throws IOException
+     */
+    public Stream<SearchResultDTO> getAllPublicResultsAsStream() throws IOException {
+        log.debug("Search request to find all public exercises");
+
+        final SearchInputMetadataDTO emptySearchMetadata = new SearchInputMetadataDTO(null, null, null, null, null, null);
+        SearchInputDTO emptySearchQuery = new SearchInputDTO(null, emptySearchMetadata, null, null, null, 0);
+
+        return metaDataRepository.streamResults(emptySearchQuery, Optional.empty(), false).getSearchStream();
+    }
+
     /**
      * Method to search for all public exercises.
-     * Searches all public exercises with an empty search query and without a specified user.
+     * Searches all public exercises with an empty search query and without a
+     * specified user.
      *
      * @return the search result page DTO
      * @throws IOException
+     * @deprecated not really reliable, because it does only return the first page
+     *             of results.
      */
+    @Deprecated
     public SearchResultsDTO searchAllPublicResults() throws IOException {
         log.debug("Search request to find all public exercises");
 
         final SearchInputMetadataDTO emptySearchMetadata = new SearchInputMetadataDTO(null, null, null, null, null, null);
         SearchInputDTO emptySearchQuery = new SearchInputDTO(null, emptySearchMetadata, null, null, null, 0);
 
-        return metaDataRepository.pageDetails(emptySearchQuery, Optional.empty());
+        return metaDataRepository.pageDetailsWithJavaApi(emptySearchQuery, Optional.empty());
     }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/StatisticsService.java b/src/main/java/at/ac/uibk/gitsearch/service/StatisticsService.java
index 0e77c3cab7a9c8dc79fd025d5d0a8812f4e68659..000322ceb8e1c0e3d52bee80fbe28706bd6c0dbc 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/StatisticsService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/StatisticsService.java
@@ -4,6 +4,7 @@ import at.ac.uibk.gitsearch.domain.Statistics;
 import at.ac.uibk.gitsearch.repository.jpa.StatisticsRepository;
 import at.ac.uibk.gitsearch.repository.search.StatisticsSearchRepository;
 import at.ac.uibk.gitsearch.service.dto.StatisticsDTO;
+import java.util.List;
 import java.util.Optional;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -91,4 +92,13 @@ public class StatisticsService {
         statisticsRepository.deleteById(id);
         statisticsSearchRepository.deleteById(id);
     }
+
+    /**
+     * returns the sum of all downloads and views of all entries.
+     *
+     * @return the two values of downlodas and views (in this sequence)
+     */
+    List<Integer[]> findNumberOfDownloadsAndViews() {
+        return statisticsRepository.findNumberOfDownloadsAndViews();
+    }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/UserService.java b/src/main/java/at/ac/uibk/gitsearch/service/UserService.java
index 8fdc12a076e03d661c9ac74607b97fcd8d828eb9..ad833aa1f2d3d57823407c42c5834c63d992ce49 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/UserService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/UserService.java
@@ -232,18 +232,10 @@ public class UserService {
                 user.setReviewingEnabled(userDTO.isReviewingEnabled());
                 user.setLangKey(userDTO.getLangKey());
                 final List<Authority> allAuthorities = authorityRepository.findAll();
-                final Set<Authority> userAuthorities = userDTO
-                    .getAuthorities()
-                    .stream()
-                    .map(authString -> new Authority(authString))
-                    .collect(Collectors.toSet());
+
+                final Set<Authority> userAuthorities = userDTO.getAuthorities().stream().map(Authority::new).collect(Collectors.toSet());
                 // insert new authorities into managedAuthoritiesTable
-                userAuthorities
-                    .stream()
-                    .filter(auth -> !allAuthorities.contains(auth))
-                    .forEach(auth -> {
-                        authorityRepository.save(auth);
-                    });
+                userAuthorities.stream().filter(auth -> !allAuthorities.contains(auth)).forEach(authorityRepository::save);
                 user.setAuthorities(userAuthorities);
                 userRepository.save(user);
                 // userSearchRepository.save(user);
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/ZipRepackagingService.java b/src/main/java/at/ac/uibk/gitsearch/service/ZipRepackagingService.java
index c89fadaac2c50f35b9993049dc1975592154b2ce..991418ea24035ba86597ff4593f1dc6c6fa37bcc 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/ZipRepackagingService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/ZipRepackagingService.java
@@ -9,7 +9,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -164,9 +163,9 @@ public class ZipRepackagingService {
             getDirPath(exerciseRoot.get().getData().getFile()),
             exerciseRoot.get()
         );
-        if (log.isWarnEnabled()) {
-            log.warn("Useing Filters: {}", applicableFilters.stream().collect(Collectors.joining(", ")));
-        }
+        //        if (log.isWarnEnabled()) {
+        //            log.warn("Useing Filters: {}", applicableFilters.stream().collect(Collectors.joining(", ")));
+        //        }
         for (String filter : applicableFilters) {
             if (filter == null) {
                 log.warn("filter should never be null!");
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/UserDTO.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/UserDTO.java
index 8ecf48a03c9df83af11fdb9c1cb73c4ed8ee3a90..4ccc302629db23b34a8ac082ddc46ce1297b0e39 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/dto/UserDTO.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/UserDTO.java
@@ -3,16 +3,22 @@ package at.ac.uibk.gitsearch.service.dto;
 import at.ac.uibk.gitsearch.config.Constants;
 import at.ac.uibk.gitsearch.domain.Authority;
 import at.ac.uibk.gitsearch.domain.User;
+import java.io.Serializable;
 import java.time.Instant;
 import java.util.Set;
 import java.util.stream.Collectors;
-import javax.validation.constraints.*;
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
 
 /**
  * A DTO representing a user, with his authorities.
  */
 @SuppressWarnings("PMD")
-public class UserDTO {
+public class UserDTO implements Serializable {
+
+    private static final long serialVersionUID = 653337653769367078L;
 
     private Long id;
 
diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/GitFilesResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/GitFilesResource.java
deleted file mode 100644
index 6d6815a91bd5aaf4aa049949aad49309ab28a477..0000000000000000000000000000000000000000
--- a/src/main/java/at/ac/uibk/gitsearch/web/rest/GitFilesResource.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package at.ac.uibk.gitsearch.web.rest;
-
-import at.ac.uibk.gitsearch.es.model.DocumentInfo;
-import at.ac.uibk.gitsearch.repository.search.GitFilesRepository;
-import at.ac.uibk.gitsearch.service.dto.GitFilesAggregationDTO;
-import java.io.IOException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * REST controller for managing {@link DocumentInfo}.
- */
-@RestController
-@RequestMapping("/api")
-@Transactional
-public class GitFilesResource {
-
-    private final Logger log = LoggerFactory.getLogger(GitFilesResource.class);
-
-    @Value("${jhipster.clientApp.name}")
-    @SuppressWarnings({ "unused", "PMD.ImmutableField" })
-    private String applicationName;
-
-    private final GitFilesRepository gitFilesRepository;
-
-    public GitFilesResource(GitFilesRepository gitFilesRepository) {
-        this.gitFilesRepository = gitFilesRepository;
-    }
-
-    /**
-     * //     * {@code SEARCH  /_search/git-files/page-details?query=:query} : search for the searchResult corresponding
-     * //     * to the query.
-     * //     *
-     * //     * @param query the query of the searchResult search.
-     * //     * @return the result of the search.
-     * //
-     */
-    /*
-    @GetMapping("/_search/git-files/page-details")
-    public GitFilesPageDetailsDTO
-    gitFilesPageDetails(@RequestParam String fulltextQuery, @RequestParam String metadataProgrammingLanguage,
-                        @RequestParam String metadataKeywords, @RequestParam String metadataNaturalLanguage,
-                        @RequestParam String metadataLicense, @RequestParam String metadataAuthor,
-                        @RequestParam List<String> selectedRepository,
-                        @RequestParam List<String> selectedUniversity, @RequestParam List<String> selectedFileFormat,
-                        @RequestParam int page, @RequestParam int pageSize) throws IOException {
-        SearchInputDTO searchInput = new SearchInputDTO(fulltextQuery, metadataProgrammingLanguage, metadataKeywords,
-            metadataNaturalLanguage, metadataLicense, metadataAuthor, selectedRepository, selectedUniversity,
-            selectedFileFormat, page);
-        log.debug("REST request to search gitFilesPageDetails for searchInput {}, pageSize {}", searchInput, pageSize);
-        // return searchResultSearchRepository.findByContent(query);
-        return gitFilesRepository.pageDetails(searchInput, pageSize);
-    }
-    */
-
-    /**
-     * //     * {@code SEARCH  /_search/git-files/aggregation?query=:query} : search for the searchResult corresponding
-     * //     * to the query.
-     * //     *
-     * //     * @param query the query of the searchResult search.
-     * //     * @return the result of the search.
-     * //
-     */
-    @GetMapping("/_search/git-files/aggregation")
-    public GitFilesAggregationDTO gitFilesAggregation(@RequestParam String query) throws IOException {
-        log.debug("REST request to search gitFilesAggregation for query {}", query);
-        // return searchResultSearchRepository.findByContent(query);
-        return gitFilesRepository.aggregation(query);
-    }
-}
diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/PluginInterfaceResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/PluginInterfaceResource.java
index afb89b92c2db2364e8ed633bd65f7e46503fa221..329661f3fdbbe11403111c3f800f46ddee9bf924 100644
--- a/src/main/java/at/ac/uibk/gitsearch/web/rest/PluginInterfaceResource.java
+++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/PluginInterfaceResource.java
@@ -101,7 +101,7 @@ public class PluginInterfaceResource {
      * @return the result of the search.
      */
     @GetMapping("/pluginIF/v0.1/basket/{basketToken}")
-    public ShoppingBasket getBasket(@PathVariable("basketToken") String basketToken) throws IOException {
+    public ShoppingBasket getBasket(@PathVariable("basketToken") String basketToken) {
         log.debug("REST request for basket  {}", basketToken);
         return basketService.getBasket(basketToken);
     }
diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
index c01df07c86c731e219acbdf77dbf18ca2fcc4d70..1a249b40e77333d535351a272ad8718a4e7b73e4 100644
--- a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
+++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
@@ -16,6 +16,7 @@ import java.nio.file.Files;
 import java.text.ParseException;
 import java.util.List;
 import java.util.Optional;
+import org.codeability.sharing.plugins.api.search.SearchResultDTO;
 import org.codeability.sharing.plugins.api.search.SearchResultsDTO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,7 +86,7 @@ public class SearchResource {
     }
 
     @GetMapping("/search/all")
-    public SearchResultsDTO searchAll() throws IOException {
+    public List<SearchResultDTO> searchAll() throws IOException {
         log.debug("REST request to search for all resources");
         return searchService.getAllResources();
     }
diff --git a/src/main/resources/keycloak-theme/themes/deploy_theme.sh b/src/main/resources/keycloak-theme/themes/deploy_theme.sh
index 70baa455a5da9be85a572ed6a067e39fbb552a0f..837df84d15dffd717037fbcc06cffe08de771498 100755
--- a/src/main/resources/keycloak-theme/themes/deploy_theme.sh
+++ b/src/main/resources/keycloak-theme/themes/deploy_theme.sh
@@ -1,9 +1,11 @@
 source ../../../docker/.env
 export GITBRANCH=test
 
-# docker cp /home/contDeploy/gitsearch2/gitsearch/src/main/resources/keycloak-theme/themes/gitsearch/. docker_keycloak_1:opt/jboss/keycloak/themes/gitsearch
+# docker cp $GITSEARCH_PATH/src/main/resources/keycloak-theme/themes/gitsearch/. docker_keycloak_1:opt/jboss/keycloak/themes/gitsearch
 cd $GITSEARCH_PATH/src/main/docker/
-docker-compose -f gitsearch.yml up  -d --build --force-recreate gitsearch-app
-docker logs docker_keycloak_1 --follow
-# docker cp docker_keycloak_1:opt/jboss/keycloak/themes/.  /home/contDeploy/gitsearch2/gitsearch/src/main/resources/keycloak-theme/themes
-# docker restart docker_keycloak_1
+#docker-compose -f gitsearch.yml up  -d --build --force-recreate gitsearch-app
+# docker logs docker_keycloak_1 --follow
+
+docker cp docker_keycloak_1:opt/jboss/keycloak/themes/gitsearch/.  $GITSEARCH_PATH/src/main/resources/keycloak-theme/themes/gitsearch
+docker restart docker_keycloak_1
+docker logs docker_keycloak_1 --follow 
\ No newline at end of file
diff --git a/src/main/resources/keycloak-theme/themes/gitsearch/login/login.ftl b/src/main/resources/keycloak-theme/themes/gitsearch/login/login.ftl
index c6cb19335fd277c423454e47bd24a253f3b07a7d..eb2ec622bc6f1dba42db619b8426bf6915fe54d6 100644
--- a/src/main/resources/keycloak-theme/themes/gitsearch/login/login.ftl
+++ b/src/main/resources/keycloak-theme/themes/gitsearch/login/login.ftl
@@ -97,14 +97,6 @@
                             </#if>
                         </a>
                     </#list>
-                    <a id="social-admin" class="pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray " type="button" href="https://search.sharing-codeability.uibk.ac.at/login">
-                                <i class="kc-social-provider-logo kc-social-gray fa fa-lock" aria-hidden="true"></i>
-                                <span class="kc-social-provider-name kc-social-icon-text">Admin Login Sharing-Prod</span>
-                        </a>
-                    <a id="social-admin" class="pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray " type="button" href="https://dev-exchange.codeability-austria.uibk.ac.at/login">
-                        <i class="kc-social-provider-logo kc-social-gray fa fa-lock" aria-hidden="true"></i>
-                        <span class="kc-social-provider-name kc-social-icon-text">Admin Login Sharing-Dev</span>
-                    </a>
                 </ul>
             </div>
         </#if>
diff --git a/src/main/resources/keycloak-theme/themes/gitsearch/login/template.ftl b/src/main/resources/keycloak-theme/themes/gitsearch/login/template.ftl
index 91a5ab0a5a2c97979c2274e223470686b5a9579f..536f06d50a23be154d364b09fcc28c74921b481b 100644
--- a/src/main/resources/keycloak-theme/themes/gitsearch/login/template.ftl
+++ b/src/main/resources/keycloak-theme/themes/gitsearch/login/template.ftl
@@ -39,7 +39,7 @@
 <body class="${properties.kcBodyClass!}">
 <div class="${properties.kcLoginClass!}">
     <div id="kc-header" class="${properties.kcHeaderClass!}">
-        <div id="kc-header-wrapper"> <img _ngcontent-juv-c133="" src="/auth/resources/xs85t/login/gitsearch/img/logo-top.png" title="codeAbility Home" alt="codeAbility" width="150px">  </div>
+        <div id="kc-header-wrapper"> <img _ngcontent-juv-c133="" src="${url.resourcesPath}/img/logo-top.png" title="codeAbility Home" alt="codeAbility" width="150px">  </div>
     </div>
     <div class="${properties.kcFormCardClass!}">
         <header class="${properties.kcFormHeaderClass!}">
diff --git a/src/main/webapp/app/admin/review-management/review-management-update/review-management-update.component.ts b/src/main/webapp/app/admin/review-management/review-management-update/review-management-update.component.ts
index cbd1cd1efc99e1c64e2b3559a6bffd0385068320..1304bc7ef7c6de17a88f2e45e81a16c2f1b0f94a 100644
--- a/src/main/webapp/app/admin/review-management/review-management-update/review-management-update.component.ts
+++ b/src/main/webapp/app/admin/review-management/review-management-update/review-management-update.component.ts
@@ -6,6 +6,7 @@ import { Account } from 'app/core/auth/account.model';
 import { AccountService } from 'app/core/auth/account.service';
 import { SearchService } from 'app/search/service/search-service';
 import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
+import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
 import { SearchResultsDTO } from 'app/shared/model/search/search-results-dto.model';
 import { ReviewManagementService } from '../review-management.service';
 import { Review } from '../review.model';
@@ -55,8 +56,8 @@ export class ReviewManagementUpdateComponent implements OnInit {
       this.users = users;
     });
     this.searchService.getAllResources().subscribe(
-      (data: SearchResultsDTO) => {
-        const searchResults = data.searchResult.map(searchResultToExercise);
+      (data: SearchResultDTO[]) => {
+        const searchResults = data.map(searchResultToExercise);
         this.results = this.results
           .concat(searchResults)
           .filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
diff --git a/src/main/webapp/app/admin/review-management/review-management.component.ts b/src/main/webapp/app/admin/review-management/review-management.component.ts
index 60f820a4c6067e765325fa57fceda4084b68d54b..cf2c3aa1d144f25bbd2b074e8b0b208ed253ba9c 100644
--- a/src/main/webapp/app/admin/review-management/review-management.component.ts
+++ b/src/main/webapp/app/admin/review-management/review-management.component.ts
@@ -4,6 +4,7 @@ import { Account } from 'app/core/auth/account.model';
 import { AccountService } from 'app/core/auth/account.service';
 import { SearchService } from 'app/search/service/search-service';
 import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
+import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
 import { SearchResultsDTO } from 'app/shared/model/search/search-results-dto.model';
 import { UserManagementService } from '../user-management/service/user-management.service';
 import { User } from '../user-management/user-management.model';
@@ -51,8 +52,8 @@ export class ReviewManagementComponent implements OnInit, OnChanges {
       this.users = users;
     });
     this.searchService.getAllResources().subscribe(
-      (data: SearchResultsDTO) => {
-        const searchResults = data.searchResult.map(searchResultToExercise);
+      (data: SearchResultDTO[]) => {
+        const searchResults = data.map(searchResultToExercise);
         this.results = this.results
           .concat(searchResults)
           .filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
diff --git a/src/main/webapp/app/search/service/search-service.ts b/src/main/webapp/app/search/service/search-service.ts
index df5ef139cbbcd8030577c7324542c44e074d5fd2..146845a5103e1dba9159a447b023d3981f23ed31 100644
--- a/src/main/webapp/app/search/service/search-service.ts
+++ b/src/main/webapp/app/search/service/search-service.ts
@@ -9,6 +9,7 @@ import { SearchInput } from 'app/shared/model/search/search-input.model';
 import { Statistics } from 'app/shared/model/statistics.model';
 import { encodeURIforExerciseId } from 'app/exercise/service/exercise.service';
 import { ReviewRequest } from 'app/admin/review-management/reviewRequest.model';
+import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
 
 @Injectable({ providedIn: 'root' })
 export class SearchService {
@@ -48,8 +49,8 @@ export class SearchService {
     return this.http.get<SearchResultsDTO>(this.resourseSearchUrlAuthor);
   }
 
-  getAllResources(): Observable<SearchResultsDTO> {
-    return this.http.get<SearchResultsDTO>('api/search/all');
+  getAllResources(): Observable<SearchResultDTO[]> {
+    return this.http.get<SearchResultDTO[]>('api/search/all');
   }
 
   requestReview(exercise: ReviewRequest): Observable<ReviewRequest> {
diff --git a/src/test/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImplIT.java b/src/test/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImplIT.java
deleted file mode 100644
index bc78635d8672660f2c44cbd4726e531001f4deb7..0000000000000000000000000000000000000000
--- a/src/test/java/at/ac/uibk/gitsearch/repository/search/GitFilesRepositoryImplIT.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package at.ac.uibk.gitsearch.repository.search;
-
-import static org.junit.jupiter.api.Assertions.fail;
-
-import at.ac.uibk.gitsearch.GitsearchApp;
-import at.ac.uibk.gitsearch.service.dto.GitFilesPageDetailsDTO;
-import java.io.IOException;
-import org.codeability.sharing.plugins.api.search.SearchInputDTO;
-import org.codeability.sharing.plugins.api.search.SearchInputMetadataDTO;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-/**
- * is test fixture is currently empty. It seems very hard, to provide trivial tests.
- * TODO: Either remove GitFilesRepository infrastructure, or provide good tests.
- * @author Michael Breu
- *
- */
-@SpringBootTest(classes = GitsearchApp.class)
-class GitFilesRepositoryImplIT {
-
-    @Autowired
-    private GitFilesRepository gitFilesRepository;
-
-    // @Test
-    void testPageDetails() throws IOException {
-        final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO(null, "testing", null, null, null, null);
-        SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
-        @SuppressWarnings("unused")
-        GitFilesPageDetailsDTO searchResultPage = gitFilesRepository.pageDetails(searchQuery);
-    }
-
-    // @Test
-    void testAggregation() {
-        fail("Not yet implemented");
-    }
-}
diff --git a/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryIT.java b/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryIT.java
index 3bb226080aa37601c327aa7cc6ca49adb50fb557..7b618f6b05ad2b308c0bd26fee97f38457bcd33a 100644
--- a/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryIT.java
@@ -5,6 +5,7 @@ import static org.hamcrest.Matchers.hasProperty;
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import at.ac.uibk.gitsearch.GitsearchApp;
 import at.ac.uibk.gitsearch.domain.util.TreeNode;
@@ -12,6 +13,8 @@ import at.ac.uibk.gitsearch.repository.search.testESService.ElasticSearchTestCon
 import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 import java.io.IOException;
 import java.text.ParseException;
+import java.time.Instant;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
@@ -19,6 +22,7 @@ import javax.ws.rs.NotFoundException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.codeability.sharing.plugins.api.search.SearchResultDTO;
+import org.codeability.sharing.plugins.api.search.SearchResultsDTO;
 import org.codeability.sharing.plugins.api.search.util.ExerciseId;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.node.NodeValidationException;
@@ -35,7 +39,6 @@ import org.springframework.boot.test.context.SpringBootTest;
 @SpringBootTest(classes = GitsearchApp.class)
 public class MetaDataRepositoryIT {
 
-    @SuppressWarnings("unused")
     private static final Logger LOGGER = LogManager.getLogger(MetaDataRepositoryIT.class);
 
     @Autowired
@@ -107,6 +110,78 @@ public class MetaDataRepositoryIT {
         }
     }
 
+    @Test
+    @Timeout(value = 5, unit = TimeUnit.MINUTES)
+    public void getByExerciseIdsSince() throws IOException {
+        // This test assumes that the test entries are public
+        for (int i : new int[] { 1 }) {
+            final String exerciseId = "[" + i + "]";
+            final Instant farAgo = Instant.ofEpochMilli(0L);
+            final SearchResultsDTO exerciseByIds = metaDataRepository.getExercisesById(
+                Collections.singleton(exerciseId).stream(),
+                Optional.empty(),
+                0,
+                100,
+                farAgo
+            );
+            assertNotNull("should be found:", exerciseByIds);
+            assertTrue(exerciseByIds.getHitCount() > 0); // at least one hit
+
+            exerciseByIds
+                .getSearchResult()
+                .forEach(exerciseById -> assertEquals("Id should be the same", exerciseId, exerciseById.getExerciseId()));
+        }
+    }
+
+    @Test
+    @Timeout(value = 5, unit = TimeUnit.MINUTES)
+    public void getByExerciseIds() throws IOException {
+        // This test assumes that the test entries are public
+        for (int i : new int[] { 1 }) {
+            final String exerciseId = "[" + i + "]";
+            final SearchResultsDTO exerciseByIds = metaDataRepository.getExercisesById(
+                Collections.singleton(exerciseId).stream(),
+                Optional.empty(),
+                0,
+                100
+            );
+            assertNotNull("should be found:", exerciseByIds);
+            assertTrue(exerciseByIds.getHitCount() > 0); // at least one hit
+
+            exerciseByIds
+                .getSearchResult()
+                .forEach(exerciseById -> assertEquals("Id should be the same", exerciseId, exerciseById.getExerciseId()));
+        }
+    }
+
+    @Test
+    @Timeout(value = 5, unit = TimeUnit.MINUTES)
+    public void getByExerciseIdsForFuture() throws IOException {
+        // This test assumes that the test entries are public
+        for (int i : new int[] { 1 }) {
+            final String exerciseId = "[" + i + "]";
+            final Instant now = Instant.now();
+            final SearchResultsDTO exerciseByIds = metaDataRepository.getExercisesById(
+                Collections.singleton(exerciseId).stream(),
+                Optional.empty(),
+                0,
+                100,
+                now
+            );
+            assertNotNull("should be found:", exerciseByIds);
+            assertEquals(0, exerciseByIds.getHitCount()); // no hit newer than now
+        }
+    }
+
+    @Test
+    public void getByEmail() throws IOException {
+        // This test assumes that the test entries are public
+
+        final SearchResultsDTO exercisesByEmail = metaDataRepository.searchByEmail("c703257@uibk.ac.at", 100, Optional.empty());
+
+        assertEquals("This may change if testdata changes", 3, exercisesByEmail.getHitCount());
+    }
+
     @Test
     public void getByExerciseIdNegative() throws IOException {
         // This test assumes that the test entries have consecutive ids
@@ -130,14 +205,12 @@ public class MetaDataRepositoryIT {
 
         Assert.assertNotNull(exerciseCollectionTree);
 
-        Assert.assertEquals(2, exerciseCollectionTree.getChildren().size()); // this
-        // may
-        // change
-        // when
-        // test
-        // data
-        // changes
-        Assert.assertEquals(2, exerciseCollectionTree.getChildren().get(0).getChildren().size()); // this may change when test data
-        // changes
+        Assert.assertEquals("This test may fail if test data changes (1)", 2, exerciseCollectionTree.getChildren().size());
+        // either first or second child contains sub-children
+        Assert.assertTrue(
+            "This test may fail if test data changes (2)",
+            exerciseCollectionTree.getChildren().get(0).getChildren().size() == 2 ||
+            exerciseCollectionTree.getChildren().get(1).getChildren().size() == 2
+        );
     }
 }
diff --git a/src/test/java/at/ac/uibk/gitsearch/repository/search/testESService/ElasticSearchTestConfiguration.java b/src/test/java/at/ac/uibk/gitsearch/repository/search/testESService/ElasticSearchTestConfiguration.java
index e90b380659a0754149943e5ea1c84ef0a878d5de..ac84ff92d5d74c0fee821d47c24560b034f47014 100644
--- a/src/test/java/at/ac/uibk/gitsearch/repository/search/testESService/ElasticSearchTestConfiguration.java
+++ b/src/test/java/at/ac/uibk/gitsearch/repository/search/testESService/ElasticSearchTestConfiguration.java
@@ -3,6 +3,8 @@ package at.ac.uibk.gitsearch.repository.search.testESService;
 import at.ac.uibk.gitsearch.domain.util.TreeNode;
 import at.ac.uibk.gitsearch.repository.search.MetaDataRepository;
 import at.ac.uibk.gitsearch.repository.search.SearchRepositoryConstants;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
 import com.fasterxml.jackson.core.JsonGenerationException;
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -34,6 +36,7 @@ import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.function.Consumer;
@@ -91,29 +94,48 @@ import org.testcontainers.utility.TestcontainersConfiguration;
 @Service
 public class ElasticSearchTestConfiguration {
 
-    static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch:7.17.2");
+    static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch:7.17.6");
 
     private ElasticsearchContainer esContainer;
 
     @Autowired
-    private RestHighLevelClient elasticsearchClient;
+    private RestHighLevelClient elasticsearchHLClient;
+
+    @Autowired
+    private ElasticsearchClient elasticsearchAPIClient;
+
+    @Autowired
+    MetaDataRepository metaDataRepository;
 
     @Value("${spring.elasticsearch.uris}")
     private String elasticSearchTestURI;
 
     public void stopTestContainer() {
         if (esContainer != null && esContainer.isCreated() && esContainer.isRunning()) {
+            SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
+            BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
+            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
+            sourceBuilder.query(queryBuilder);
+
+            try {
+                SearchResponse searchResponse = elasticsearchHLClient.search(searchRequest, RequestOptions.DEFAULT);
+                LOGGER.info("Test ES still containing {} entries before shut down", searchResponse.getHits().getTotalHits());
+            } catch (IOException e1) {
+                LOGGER.info("Last Search not working", e1);
+            }
+
             esContainer.stop();
             esContainer.close();
             while (esContainer.isRunning()) {
                 try {
                     Thread.sleep(1000);
-                    LOGGER.info("Shutting down test container...");
+                    LOGGER.info("Shutting down esContainer ...");
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             esContainer = null;
+            LOGGER.warn("Shutted down ES Test Node");
         }
     }
 
@@ -121,7 +143,7 @@ public class ElasticSearchTestConfiguration {
 
     private int getElasticSearchPort() {
         try {
-            URI es = URI.create(elasticSearchTestURI);
+            URI es = URI.create(elasticSearchTestURI.split(",")[0]);
             return es.getPort();
         } catch (IllegalArgumentException | NullPointerException e) {
             LOGGER.warn("Cannot parse " + elasticSearchTestURI + " to get the port");
@@ -330,6 +352,7 @@ public class ElasticSearchTestConfiguration {
 
             setUpMetaDataIndex(hostURL);
             setUpContent(hostURL);
+            metaDataRepository.updateAutocompletionCache();
             LOGGER.info("Started ES Test Node");
         } catch (Throwable t) {
             LOGGER.info("ES Test Node crashed: ", t);
@@ -337,8 +360,20 @@ public class ElasticSearchTestConfiguration {
         }
     }
 
+    /**
+     * redirects the clients to the docker instance.
+     * Is there are more elegant way to mock es node urls?
+     *
+     * @param hostURL
+     * @throws IOException
+     */
     private void setUpESClientForTestESServer(HttpHost hostURL) throws IOException {
-        elasticsearchClient.getLowLevelClient().setNodes(RestClient.builder(hostURL).build().getNodes());
+        elasticsearchHLClient.getLowLevelClient().setNodes(RestClient.builder(hostURL).build().getNodes());
+
+        org.elasticsearch.client.Node n = new org.elasticsearch.client.Node(new HttpHost(hostURL));
+
+        final RestClientTransport esTransport = (RestClientTransport) elasticsearchAPIClient._transport();
+        esTransport.restClient().setNodes(Collections.singleton(n));
     }
 
     private void setUpMetaDataIndex(HttpHost hostURL) throws IOException {
@@ -393,7 +428,7 @@ public class ElasticSearchTestConfiguration {
         // parse the JSON response
         List<HashMap<String, String>> list = null;
         if (response2 != null) {
-            ObjectMapper mapper = new ObjectMapper();
+            ObjectMapper mapper = MetaDataRepository.getSearchResultObjectMapper();
             String rawBody = EntityUtils.toString(response2.getEntity());
             TypeReference<List<HashMap<String, String>>> typeRef = new TypeReference<List<HashMap<String, String>>>() {};
             list = mapper.readValue(rawBody, typeRef);
@@ -647,7 +682,7 @@ public class ElasticSearchTestConfiguration {
      * @param id
      */
     public void updateMetadata(String content, String id) {
-        String elURL = elasticsearchClient.getLowLevelClient().getNodes().get(0).getHost().toURI();
+        String elURL = elasticsearchHLClient.getLowLevelClient().getNodes().get(0).getHost().toURI();
         RestTemplate restTemplate = new RestTemplate();
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_JSON);
@@ -657,12 +692,12 @@ public class ElasticSearchTestConfiguration {
 
     public String updateMetadata(SearchResultDTO testExercise, String id)
         throws JsonGenerationException, JsonMappingException, IOException {
-        String elURL = elasticsearchClient.getLowLevelClient().getNodes().get(0).getHost().toURI();
+        String elURL = elasticsearchHLClient.getLowLevelClient().getNodes().get(0).getHost().toURI();
         // first fetch previous content
         RestTemplate restTemplate = new RestTemplate();
         String previousContent = restTemplate.getForObject(elURL + "/metadata/_doc/" + id, String.class);
 
-        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectMapper objectMapper = MetaDataRepository.getSearchResultObjectMapper();
         final JavaTimeModule javaModule = new JavaTimeModule();
 
         objectMapper.registerModule(javaModule);
@@ -706,7 +741,7 @@ public class ElasticSearchTestConfiguration {
      * @param minExpectedHits
      */
     private void waitForCleanStartUp(int minExpectedHits) {
-        RestHighLevelClient client = elasticsearchClient;
+        RestHighLevelClient client = elasticsearchHLClient;
         int tryCount = 0;
         while (tryCount < 10) {
             tryCount++;
diff --git a/src/test/java/at/ac/uibk/gitsearch/service/MonitoringServiceIT.java b/src/test/java/at/ac/uibk/gitsearch/service/MonitoringServiceIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..6eac1f273ea4fe672ea306b3e1b4f790713ac44b
--- /dev/null
+++ b/src/test/java/at/ac/uibk/gitsearch/service/MonitoringServiceIT.java
@@ -0,0 +1,212 @@
+package at.ac.uibk.gitsearch.service;
+
+import static at.ac.uibk.gitsearch.service.MonitoringService.HOURLY_TAG;
+import static at.ac.uibk.gitsearch.service.MonitoringService.NUMBER_DOWNLOAD_NAME;
+import static at.ac.uibk.gitsearch.service.MonitoringService.NUMBER_EXERCISE_NAME;
+import static at.ac.uibk.gitsearch.service.MonitoringService.NUMBER_LIKE_NAME;
+import static at.ac.uibk.gitsearch.service.MonitoringService.NUMBER_USER_NAME;
+import static at.ac.uibk.gitsearch.service.MonitoringService.NUMBER_VIEW_NAME;
+import static at.ac.uibk.gitsearch.service.MonitoringService.USER_TAG;
+
+import at.ac.uibk.gitsearch.IntegrationTest;
+import at.ac.uibk.gitsearch.domain.Likes;
+import at.ac.uibk.gitsearch.domain.User;
+import at.ac.uibk.gitsearch.repository.jpa.StatisticsRepository;
+import at.ac.uibk.gitsearch.repository.search.testESService.ElasticSearchTestConfiguration;
+import at.ac.uibk.gitsearch.service.dto.AdminUserDTO;
+import at.ac.uibk.gitsearch.service.dto.StatisticsDTO;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.Measurement;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.search.RequiredSearch;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.elasticsearch.node.NodeValidationException;
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.transaction.annotation.Transactional;
+
+@IntegrationTest
+@WithMockUser(value = "test", authorities = "sharing")
+public class MonitoringServiceIT {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOGGER = LoggerFactory.getLogger(MonitoringServiceIT.class);
+
+    @Autowired
+    private MonitoringService monitoringService;
+
+    @Autowired
+    private StatisticsService statisticsService;
+
+    @Autowired
+    private StatisticsRepository statisticsRepository;
+
+    @Autowired
+    PrometheusMeterRegistry hourlyMeterRegistry;
+
+    @Autowired
+    private LikesService likesService;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    MeterRegistry meterRegistry;
+
+    @Autowired
+    private SearchService searchService;
+
+    @Autowired
+    ElasticSearchTestConfiguration elasticSearchTestConfiguration;
+
+    static ElasticSearchTestConfiguration staticElasticSearchTestConfiguration;
+
+    @BeforeEach // cleanup all likes
+    @AfterEach // before and after
+    @Transactional
+    public void cleanUpLikesEtAll() {
+        likesService.findAll().stream().forEach(l -> likesService.delete(l.getId()));
+        statisticsRepository.findAll().stream().forEach(s -> statisticsRepository.delete(s));
+    }
+
+    @BeforeEach // must be started as BeforeEach, in order to autowire the ElasticSearchTestConfiguration
+    @Timeout(value = 3, unit = TimeUnit.MINUTES)
+    public void setUpESServer() throws IOException, NodeValidationException {
+        if (staticElasticSearchTestConfiguration == null) {
+            elasticSearchTestConfiguration.startTestNode();
+            staticElasticSearchTestConfiguration = elasticSearchTestConfiguration;
+        }
+    }
+
+    @AfterAll
+    @Timeout(value = 2, unit = TimeUnit.MINUTES)
+    public static void shutDownESServer() {
+        if (staticElasticSearchTestConfiguration != null) {
+            staticElasticSearchTestConfiguration.stopTestContainer();
+            staticElasticSearchTestConfiguration = null;
+        }
+    }
+
+    @Test
+    @Timeout(value = 5, unit = TimeUnit.MINUTES)
+    @WithAnonymousUser
+    public void testMonitorHourlyMetrics() throws IOException {
+        Random random = new Random(123L);
+
+        AdminUserDTO user = new AdminUserDTO();
+        user.setFirstName("first");
+        user.setLastName("last");
+        user.setEmail("abc@de.de");
+        user.setLogin("someUser");
+        user.setLastLogin(Instant.now());
+        user.setActivated(true);
+        User u = userService.createUser(user);
+
+        AtomicInteger likes = new AtomicInteger(0);
+        AtomicInteger downloads = new AtomicInteger(0);
+        AtomicInteger views = new AtomicInteger(0);
+        AtomicInteger exerciseCount = new AtomicInteger(0);
+
+        searchService
+            .getAllResultsAsStream()
+            .forEach(ex -> {
+                exerciseCount.addAndGet(1);
+                StatisticsDTO stats = new StatisticsDTO();
+                stats.setExerciseID(ex.getExerciseId());
+                stats.setViews(random.nextInt(100));
+                stats.setDownloads(random.nextInt(100));
+                statisticsService.save(stats);
+                downloads.addAndGet(stats.getDownloads());
+                views.addAndGet(stats.getViews());
+
+                if (random.nextBoolean()) {
+                    Likes like = new Likes();
+                    like.date(LocalDate.now()).exerciseID(ex.getExerciseId()).userID(u.getId().intValue());
+                    likesService.save(like);
+                    likes.incrementAndGet();
+                }
+            });
+
+        monitoringService.monitorHourlyMetrics();
+
+        // Countercheck results against original list
+        Assert.assertEquals(
+            "User counter may fail, if test data changes",
+            5.0d,
+            getLastMeterValue(NUMBER_USER_NAME, Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "All"))),
+            0.0001d
+        );
+        Assert.assertEquals(
+            "Activae User counter may fail, if test data changes",
+            0.0d,
+            getLastMeterValue(NUMBER_USER_NAME, Tags.of(HOURLY_TAG, Tag.of(USER_TAG, "Active"))),
+            0.0001d
+        );
+        Assert.assertEquals(
+            "likes counter may fail, if test data changes",
+            likes.doubleValue(),
+            getLastMeterValue(NUMBER_LIKE_NAME),
+            0.0001d
+        );
+        Assert.assertEquals(
+            "View counter may fail, if test data changes",
+            views.doubleValue(),
+            getLastMeterValue(NUMBER_VIEW_NAME),
+            1.0001d
+        );
+        Assert.assertEquals(
+            "Download counter may fail, if test data changes",
+            downloads.doubleValue(),
+            getLastMeterValue(NUMBER_DOWNLOAD_NAME),
+            1.0001d
+        );
+        Assert.assertEquals(
+            "Exercise counter may fail, if test data changes",
+            exerciseCount.doubleValue(),
+            getLastMeterValue(NUMBER_EXERCISE_NAME),
+            0.0001d
+        );
+        Assert.assertEquals(
+            "Language counter may fail, if test data changes",
+            11.0d,
+            getLastMeterValue(NUMBER_EXERCISE_NAME, Tags.of(HOURLY_TAG, Tag.of(MonitoringService.LANGUAGE_TAG, "JAVA"))),
+            0.0001d
+        );
+        Assert.assertEquals(
+            "Language counter may fail, if test data changes",
+            5.0d,
+            getLastMeterValue(NUMBER_EXERCISE_NAME, Tags.of(HOURLY_TAG, Tag.of(MonitoringService.LANGUAGE_TAG, "Unclassified"))),
+            0.0001d
+        );
+    }
+
+    public double getLastMeterValue(String name) {
+        final Gauge gauge = RequiredSearch.in(hourlyMeterRegistry).name(name).gauge();
+        final Measurement next = gauge.measure().iterator().next();
+        //        System.out.println(next);
+        return next.getValue();
+    }
+
+    public double getLastMeterValue(String name, Tags tags) {
+        final Gauge gauge = RequiredSearch.in(hourlyMeterRegistry).name(name).tags(tags).gauge();
+
+        return gauge.value();
+    }
+}
diff --git a/src/test/java/at/ac/uibk/gitsearch/service/SearchServiceIT.java b/src/test/java/at/ac/uibk/gitsearch/service/SearchServiceIT.java
index b2bdf4b4d9f5bfba411cc2e5ea772d3e3546794a..65c8c3ddbe6cba96709e209fedf63fcf8cbcf696 100644
--- a/src/test/java/at/ac/uibk/gitsearch/service/SearchServiceIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/service/SearchServiceIT.java
@@ -1,15 +1,13 @@
 package at.ac.uibk.gitsearch.service;
 
-import static org.hamcrest.CoreMatchers.containsStringIgnoringCase;
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.containsStringIgnoringCase;
 import static org.hamcrest.Matchers.everyItem;
 import static org.hamcrest.Matchers.hasItemInArray;
 import static org.hamcrest.Matchers.hasProperty;
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,6 +29,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import javax.ws.rs.NotFoundException;
 import org.codeability.sharing.plugins.api.SharingPluginConfig;
 import org.codeability.sharing.plugins.api.search.SearchInputDTO;
@@ -43,11 +42,11 @@ import org.gitlab4j.api.GitLabApi;
 import org.gitlab4j.api.GitLabApiException;
 import org.gitlab4j.api.RepositoryApi;
 import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 import org.slf4j.Logger;
@@ -190,22 +189,6 @@ public class SearchServiceIT {
         );
     }
 
-    @Test
-    @Timeout(value = 1, unit = TimeUnit.MINUTES)
-    @Disabled("Ich brings nicht zum laufen :-(")
-    public void testProgrammingLanguageSearch() throws Exception {
-        final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO("Java", null, null, null, null, null);
-        SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
-        SearchResultsDTO searchResultPage = searchService.searchResultPage(searchQuery);
-
-        assertNotNull(searchResultPage.getSearchResult());
-        assertTrue("At least one test hit", searchResultPage.getHitCount() >= 1);
-        MatcherAssert.assertThat(
-            searchResultPage.getSearchResult(),
-            everyItem(hasProperty("metadata", hasProperty("programmingLanguage", hasItemInArray(containsStringIgnoringCase("Java")))))
-        );
-    }
-
     @Test
     @Timeout(value = 1, unit = TimeUnit.MINUTES)
     public void testCreatorSearch() throws IOException {
@@ -275,7 +258,7 @@ public class SearchServiceIT {
 
     @Test
     @Timeout(value = 1, unit = TimeUnit.MINUTES)
-    public void testLanguagesSearch() throws IOException {
+    public void testNaturalLanguagesSearch() throws IOException {
         final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO(null, "latex", null, null, null, null);
         searchMetadata.setNaturalLanguage(List.of("de"));
         SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
@@ -289,6 +272,36 @@ public class SearchServiceIT {
         );
     }
 
+    @Test
+    @Timeout(value = 1, unit = TimeUnit.MINUTES)
+    public void testProgrammingLanguagesSearch() throws IOException {
+        final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO("Java", null, null, null, null, null);
+        SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
+        SearchResultsDTO searchResultPage = searchService.searchResultPage(searchQuery);
+        org.junit.Assert.assertNotNull(searchResultPage.getSearchResult());
+
+        org.junit.Assert.assertTrue("At least one test hit", searchResultPage.getHitCount() >= 1);
+        MatcherAssert.assertThat(
+            searchResultPage.getSearchResult(),
+            everyItem(hasProperty("metadata", hasProperty("programmingLanguage", hasItemInArray(containsStringIgnoringCase("Java")))))
+        );
+    }
+
+    @Test
+    @Timeout(value = 1, unit = TimeUnit.MINUTES)
+    public void testProgrammingLanguagesPrefixSearch() throws IOException {
+        final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO("Jav", null, null, null, null, null);
+        SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
+        SearchResultsDTO searchResultPage = searchService.searchResultPage(searchQuery);
+        org.junit.Assert.assertNotNull(searchResultPage.getSearchResult());
+
+        org.junit.Assert.assertTrue("At least one test hit", searchResultPage.getHitCount() >= 1);
+        MatcherAssert.assertThat(
+            searchResultPage.getSearchResult(),
+            everyItem(hasProperty("metadata", hasProperty("programmingLanguage", hasItemInArray(containsStringIgnoringCase("Java")))))
+        );
+    }
+
     // does not work with metadata version 0.4
     //	@Test
     //	public void testTypesSearchNegative() throws IOException {
@@ -361,6 +374,7 @@ public class SearchServiceIT {
 
         final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO(null, null, null, null, null, null);
         SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
+        searchQuery.setPageSize(100);
         SearchResultsDTO results = searchService.searchResultPage(searchQuery);
 
         Optional<SearchResultDTO> result = results
@@ -384,6 +398,7 @@ public class SearchServiceIT {
     public void testArtemisExportActionFilteringForUnauthorizedUser() throws IOException {
         final SearchInputMetadataDTO searchMetadata = new SearchInputMetadataDTO(null, null, null, null, null, null);
         SearchInputDTO searchQuery = new SearchInputDTO(null, searchMetadata, null, null, null, 0);
+        searchQuery.setPageSize(100);
         SearchResultsDTO results = searchService.searchResultPage(searchQuery);
 
         Optional<SearchResultDTO> result = results
@@ -411,4 +426,35 @@ public class SearchServiceIT {
         Assert.assertEquals("We start at 0", 0, searchResults.getPageStartIndex());
         LOGGER.info("found {} hits for search all", searchResults.getHitCount());
     }
+
+    @Test
+    @Timeout(value = 1, unit = TimeUnit.MINUTES)
+    @WithAnonymousUser
+    public void testSearchStreamAllPublic() throws IOException {
+        final List<SearchResultDTO> allPublicSearchResults = searchService.getAllPublicResultsAsStream().collect(Collectors.toList());
+
+        Assert.assertTrue("At least one hit?", allPublicSearchResults.size() >= 1);
+        Assert.assertTrue("At least more than one page full?", allPublicSearchResults.size() > 10);
+
+        SearchResultsDTO searchResults = searchService.searchAllPublicResults();
+
+        // only the first 10 results are contained in the other list
+        MatcherAssert.assertThat(
+            allPublicSearchResults.subList(0, 10),
+            Matchers.containsInAnyOrder(searchResults.getSearchResult().toArray(new SearchResultDTO[0]))
+        );
+    }
+
+    @Test
+    @Timeout(value = 1, unit = TimeUnit.MINUTES)
+    @WithAnonymousUser
+    public void testSearchStreamAll() throws IOException {
+        final List<SearchResultDTO> allSearchResults = searchService.getAllResultsAsStream().collect(Collectors.toList());
+        final List<SearchResultDTO> allPublicSearchResults = searchService.getAllPublicResultsAsStream().collect(Collectors.toList());
+
+        Assert.assertTrue("At least one hit?", allSearchResults.size() >= 1);
+        Assert.assertTrue("At least more than one page full?", allSearchResults.size() > 10);
+
+        Assert.assertTrue("At at least one non public result?", allSearchResults.size() > allPublicSearchResults.size());
+    }
 }
diff --git a/src/test/java/at/ac/uibk/gitsearch/web/rest/LikesResourceIT.java b/src/test/java/at/ac/uibk/gitsearch/web/rest/LikesResourceIT.java
index fa00740df5bc2932a3d04cd6f8e0760ffc39031a..fc5bba0189aee2484ee51b65f69580f9baa1285d 100644
--- a/src/test/java/at/ac/uibk/gitsearch/web/rest/LikesResourceIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/web/rest/LikesResourceIT.java
@@ -115,6 +115,12 @@ public class LikesResourceIT {
         likes = createEntity(em);
     }
 
+    @BeforeEach // cleanup all likes
+    @Transactional
+    public void cleanUpLikes() {
+        likesService.findAll().stream().forEach(l -> likesService.delete(l.getId()));
+    }
+
     @Test
     @Transactional
     public void createLikes() throws Exception {
diff --git a/src/test/java/at/ac/uibk/gitsearch/web/rest/SearchResourceIT.java b/src/test/java/at/ac/uibk/gitsearch/web/rest/SearchResourceIT.java
index 6d5aff504892c743b3549311341cf744e1fd2a38..7e8b185ac5c67481b380ddbe33cc1d28ff8451b2 100644
--- a/src/test/java/at/ac/uibk/gitsearch/web/rest/SearchResourceIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/web/rest/SearchResourceIT.java
@@ -126,6 +126,7 @@ public class SearchResourceIT {
     }
 
     @Test
+    @Timeout(value = 5, unit = TimeUnit.MINUTES)
     public void getREADME() throws Exception {
         //    	String exerciseId = "272"; // warning: this depends on the sequence in the current search index :-(
         //    	restSearchMockMvc.perform(post("/api/exerciseFiles/"+exerciseId)
diff --git a/src/test/java/at/ac/uibk/gitsearch/web/rest/UserResourceIT.java b/src/test/java/at/ac/uibk/gitsearch/web/rest/UserResourceIT.java
index 1c34aae25f67992f6bee270bf4fa8b4c26806c97..5381752a6031985d6daf70db5fec3adf6403a80a 100644
--- a/src/test/java/at/ac/uibk/gitsearch/web/rest/UserResourceIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/web/rest/UserResourceIT.java
@@ -609,7 +609,7 @@ class UserResourceIT {
     void testAuthorityEquals() {
         Authority authorityA = new Authority();
         assertThat(authorityA).isNotEqualTo(null).isNotEqualTo(new Object());
-        assertThat(authorityA.hashCode()).isZero();
+        assertThat(authorityA.hashCode()).isNotZero(); // useless test?
         assertThat(authorityA.toString()).isNotNull();
 
         Authority authorityB = new Authority();
diff --git a/src/test/resources/at/ac/uibk/gitsearch/repository/search/testData/es_metadata.schema.json b/src/test/resources/at/ac/uibk/gitsearch/repository/search/testData/es_metadata.schema.json
index a12979e61a915fb4b9c0cf58b11b4c167d282bc3..9ca1a145b283bfe4cb0dbc9c094661cc31faafbd 100644
--- a/src/test/resources/at/ac/uibk/gitsearch/repository/search/testData/es_metadata.schema.json
+++ b/src/test/resources/at/ac/uibk/gitsearch/repository/search/testData/es_metadata.schema.json
@@ -1,29 +1,79 @@
 {
   "mappings": {
     "properties": {
-      "file": {
+      "project": {
         "properties": {
-          "children": {
+          "project_id": {
+            "type": "long"
+          },
+          "project_name": {
             "type": "keyword"
           },
-          "commit_id": {
+          "url": {
+            "type": "keyword"
+          },
+          "namespace": {
+            "type": "keyword"
+          },
+          "main_group": {
+            "type": "keyword"
+          },
+          "sub_group": {
             "type": "keyword"
           },
+          "visibility": {
+            "type": "keyword"
+          },
+          "users": {
+            "type": "keyword"
+          },
+          "groups": {
+            "type": "keyword"
+          },
+          "archived": {
+            "type": "boolean"
+          },
+          "star_count": {
+            "type": "integer"
+          },
+          "open_issues_count": {
+            "type": "integer"
+          },
+          "forks_count": {
+            "type": "integer"
+          },
+          "last_activity_at": {
+            "type": "date",
+            "format": "date_optional_time"
+          },
+          "description": {
+            "type": "text"
+          }
+        }
+      },
+      "file": {
+        "properties": {
           "filename": {
             "type": "keyword"
           },
+          "path": {
+            "type": "keyword"
+          },
+          "commit_id": {
+            "type": "keyword"
+          },
           "indexing_date": {
             "type": "date",
-            "format": "dateOptionalTime"
+            "format": "date_optional_time"
           },
           "last_activity_at": {
             "type": "date",
-            "format": "dateOptionalTime"
+            "format": "date_optional_time"
           },
           "parentId": {
             "type": "keyword"
           },
-          "path": {
+          "children": {
             "type": "keyword"
           }
         }
@@ -36,31 +86,28 @@
           "audience": {
             "type": "text"
           },
-          "collectionContent": {
-            "type": "keyword"
-          },
           "contributor": {
             "properties": {
-              "affiliation": {
+              "name": {
                 "type": "text"
               },
-              "email": {
+              "affiliation": {
                 "type": "text"
               },
-              "name": {
+              "email": {
                 "type": "text"
               }
             }
           },
           "creator": {
             "properties": {
-              "affiliation": {
+              "name": {
                 "type": "text"
               },
-              "email": {
+              "affiliation": {
                 "type": "text"
               },
-              "name": {
+              "email": {
                 "type": "text"
               }
             }
@@ -84,7 +131,7 @@
             "type": "text"
           },
           "format": {
-            "type": "text"
+            "type": "keyword"
           },
           "identifier": {
             "type": "keyword"
@@ -110,12 +157,6 @@
           "license": {
             "type": "keyword"
           },
-          "metadataVersion": {
-            "type": "keyword"
-          },
-          "programmingLanguage": {
-            "type": "text"
-          },
           "publicVisibility": {
             "properties": {
               "except": {
@@ -123,15 +164,21 @@
               }
             }
           },
+          "metadataVersion": {
+            "type": "keyword"
+          },
+          "programmingLanguage": {
+            "type": "keyword"
+          },
           "publisher": {
             "properties": {
-              "affiliation": {
+              "name": {
                 "type": "text"
               },
-              "email": {
+              "affiliation": {
                 "type": "text"
               },
-              "name": {
+              "email": {
                 "type": "text"
               }
             }
@@ -174,56 +221,6 @@
             "type": "keyword"
           }
         }
-      },
-      "project": {
-        "properties": {
-          "archived": {
-            "type": "boolean"
-          },
-          "description": {
-            "type": "text"
-          },
-          "forks_count": {
-            "type": "integer"
-          },
-          "groups": {
-            "type": "keyword"
-          },
-          "last_activity_at": {
-            "type": "date",
-            "format": "dateOptionalTime"
-          },
-          "main_group": {
-            "type": "keyword"
-          },
-          "namespace": {
-            "type": "keyword"
-          },
-          "open_issues_count": {
-            "type": "integer"
-          },
-          "project_id": {
-            "type": "long"
-          },
-          "project_name": {
-            "type": "keyword"
-          },
-          "star_count": {
-            "type": "integer"
-          },
-          "sub_group": {
-            "type": "keyword"
-          },
-          "url": {
-            "type": "keyword"
-          },
-          "users": {
-            "type": "keyword"
-          },
-          "visibility": {
-            "type": "keyword"
-          }
-        }
       }
     }
   }
diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml
index 4f229503b7d94bdf8ad737f90d6f1263edbff9dd..5ab560fc3173aa345742fda2841f307e66b22857 100644
--- a/src/test/resources/config/application.yml
+++ b/src/test/resources/config/application.yml
@@ -14,9 +14,14 @@
 # ===================================================================
 logging:
   level:
-    ROOT: DEBUG
-    tech.jhipster: DEBUG
-    org.hibernate.SQL: DEBUG
+    ROOT: WARN
+
+    tech.jhipster: WARN
+    org.hibernate.SQL: WARN
+    org.springframework: WARN
+    org.springframework.web: WARN
+    org.springframework.security: WARN
+    org.zalando: ERROR
     at.ac.uibk.gitsearch: DEBUG
     org.glassfish.jersey.client.ClientExecutorProvidersConfigurator: INFO
     javax.management.mbeanserver: INFO
diff --git a/src/test/resources/other/soapUI/localhost-29200-soapui-project.xml b/src/test/resources/other/soapUI/localhost-29200-soapui-project.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a396932bab17af170af4b573ed39549ae526eda6
--- /dev/null
+++ b/src/test/resources/other/soapUI/localhost-29200-soapui-project.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<con:soapui-project id="e5e6ec50-ebc6-40b2-b869-94fd32b98793" activeEnvironment="Default" name="localhost:29200" resourceRoot="" soapui-version="5.7.0" xmlns:con="http://eviware.com/soapui/config"><con:settings/><con:interface xsi:type="con:RestService" id="5e1934cd-82a4-4bba-9ac0-cca93457c9fb" wadlVersion="http://wadl.dev.java.net/2009/02" name="http://localhost:29200" type="rest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><con:settings/><con:definitionCache/><con:endpoints><con:endpoint>http://localhost:29200</con:endpoint></con:endpoints><con:resource name="" path="/metadata/_search?pretty" id="b3276207-be96-4f21-8069-7490845e0d2e"><con:settings/><con:parameters/><con:method name="1" id="af5694fe-1ad1-4f25-989b-bd826e1a04b2" method="POST"><con:settings/><con:parameters/><con:representation type="REQUEST"><con:mediaType>application/json</con:mediaType><con:params/></con:representation><con:representation type="FAULT"><con:mediaType>application/json; charset=UTF-8</con:mediaType><con:status>400</con:status><con:params/><con:element xmlns:_se="http://localhost/metadata/_search/">_se:Fault</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType>application/json; charset=UTF-8</con:mediaType><con:status>200</con:status><con:params/><con:element xmlns:_se="http://localhost/metadata/_search/">_se:Response</con:element></con:representation><con:request name="Request 1" id="ee6d6b05-b9a9-4d5d-99e2-d4b2cfb1c92c" mediaType="application/json" postQueryString="false"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://localhost:29200</con:endpoint><con:request>{
+    "query": {
+    	  "term":  {
+                                "metadata.language":  "de"
+    	  }
+    }
+}
+</con:request><con:originalUri>http://localhost/metadata/_search/</con:originalUri><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters/><con:parameterOrder/></con:request></con:method></con:resource><con:resource name="search Test2" path="/metadata/_search?pretty" id="b3276207-be96-4f21-8069-7490845e0d2e"><con:settings/><con:parameters/><con:method name="1" id="af5694fe-1ad1-4f25-989b-bd826e1a04b2" method="POST"><con:settings/><con:parameters/><con:representation type="REQUEST"><con:mediaType>application/json</con:mediaType><con:params/></con:representation><con:representation type="FAULT"><con:mediaType>application/json; charset=UTF-8</con:mediaType><con:status>400</con:status><con:params/><con:element xmlns:_se="http://localhost/metadata/_search/">_se:Fault</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType>application/json; charset=UTF-8</con:mediaType><con:status>200</con:status><con:params/><con:element xmlns:_se="http://localhost/metadata/_search/">_se:Response</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:request name="Request 1" id="ee6d6b05-b9a9-4d5d-99e2-d4b2cfb1c92c" mediaType="application/json" postQueryString="false"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://localhost:29200</con:endpoint><con:request>{
+    "query": {
+        "bool": {
+            "must": [
+                {
+                    "bool": {
+                        "should": [
+                            {
+                                "match_phrase_prefix": {
+                                    "metadata.keyword": {
+                                        "query": "latex"
+                                    }
+                                }
+                            }
+                        ]
+                    }
+                },
+                {
+                    "bool": {
+                        "should": [
+                            {
+                                "match": {
+                                    "metadata.language": {
+                                        "query": "de"
+                                    }
+                                }
+                            }
+                        ]
+                    }
+                },
+                {
+                    "bool": {
+                        "should": [
+                            {
+                                "term": {
+                                    "project.visibility": {
+                                        "boost": 0.0,
+                                        "value": "public"
+                                    }
+                                }
+                            }
+                        ]
+                    }
+                }
+            ]
+        }
+    }
+}</con:request><con:originalUri>http://localhost/metadata/_search/</con:originalUri><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters/><con:parameterOrder/></con:request></con:method></con:resource></con:interface><con:properties/><con:wssContainer/><con:oAuth2ProfileContainer/><con:oAuth1ProfileContainer/></con:soapui-project>
\ No newline at end of file