diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd6ec57e..eca3c7bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Changed
+
+- **BREAKING:** Upgrade Groovy from 4.0.31 to 5.0.6
+- **BREAKING:** Drop Java 8 support — minimum is now Java 11 (Groovy 5.x requires JDK 11+)
+- Bump exported API package versions to 20.0.0
+
## [19.1.0] - 2026-05-04
### Changed
diff --git a/README.md b/README.md
index 383c0946..f42750de 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,9 @@ in the package for reference.
## Compatibility
-AEM Groovy Console 19.0.8+ runs on Java 8, 11, 17 & 21 with an embedded Groovy version of 4.0.22
+AEM Groovy Console 20.x requires **Java 11, 17 or 21** with an embedded Groovy version of 5.0.6.
+
+For environments still on Java 8, use AEM Groovy Console 19.x (last release on the Groovy 4.x line).
Supported versions:
diff --git a/all/pom.xml b/all/pom.xml
index c0bc45d8..18864a20 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -6,7 +6,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-all
diff --git a/api/pom.xml b/api/pom.xml
index 49ae7ef3..106e16ac 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -8,7 +8,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-api
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/impl/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/impl/package-info.groovy
index 21e83a7d..006b8b64 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/impl/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/impl/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.api.context.impl;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/package-info.groovy
index 5cd71463..49a7b7a9 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/context/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.api.context;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/package-info.groovy
index 19ca69f7..df2b767a 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/api/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/api/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.api;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/audit/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/audit/package-info.groovy
index b8faa8a6..291c48fe 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/audit/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/audit/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.1.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.audit;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/configuration/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/configuration/package-info.groovy
index 37697910..7fb8ecb1 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/configuration/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/configuration/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.configuration;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/constants/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/constants/package-info.groovy
index 5f131794..cfd583d8 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/constants/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/constants/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.1")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.constants;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/extension/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/extension/package-info.groovy
index 3009b3a4..597d9a4b 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/extension/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/extension/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.extension;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/notification/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/notification/package-info.groovy
index 0785fa23..9175b653 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/notification/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/notification/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.notification;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/package-info.groovy
index a05e6853..4ab242ab 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/response/impl/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/response/impl/package-info.groovy
index 7df8d89c..309d56d8 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/response/impl/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/response/impl/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.response.impl;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/response/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/response/package-info.groovy
index ac5c5346..6615452b 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/response/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/response/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.response;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/table/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/table/package-info.groovy
index d3dea61b..c1d3e60d 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/table/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/table/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.table;
diff --git a/api/src/main/groovy/be/orbinson/aem/groovy/console/utils/package-info.groovy b/api/src/main/groovy/be/orbinson/aem/groovy/console/utils/package-info.groovy
index 289e0c3f..af926631 100644
--- a/api/src/main/groovy/be/orbinson/aem/groovy/console/utils/package-info.groovy
+++ b/api/src/main/groovy/be/orbinson/aem/groovy/console/utils/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.utils;
diff --git a/bundle/pom.xml b/bundle/pom.xml
index 3e0c350b..2f1ed0c1 100644
--- a/bundle/pom.xml
+++ b/bundle/pom.xml
@@ -8,7 +8,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-bundle
diff --git a/bundle/src/main/groovy/be/orbinson/aem/groovy/console/components/package-info.groovy b/bundle/src/main/groovy/be/orbinson/aem/groovy/console/components/package-info.groovy
index d2c7bfe0..cd67a499 100644
--- a/bundle/src/main/groovy/be/orbinson/aem/groovy/console/components/package-info.groovy
+++ b/bundle/src/main/groovy/be/orbinson/aem/groovy/console/components/package-info.groovy
@@ -1,3 +1,3 @@
@org.osgi.annotation.bundle.Export
-@org.osgi.annotation.versioning.Version("19.0.0")
+@org.osgi.annotation.versioning.Version("20.0.0")
package be.orbinson.aem.groovy.console.components;
diff --git a/groovy/groovy-osgi/pom.xml b/groovy/groovy-osgi/pom.xml
index 3f0500f9..6317cfa8 100644
--- a/groovy/groovy-osgi/pom.xml
+++ b/groovy/groovy-osgi/pom.xml
@@ -8,7 +8,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
../../pom.xml
diff --git a/groovy/pom.xml b/groovy/pom.xml
index 54a21c7a..5e2b02d2 100644
--- a/groovy/pom.xml
+++ b/groovy/pom.xml
@@ -8,7 +8,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
pom
diff --git a/it.tests/pom.xml b/it.tests/pom.xml
index 553b7e01..ede024c7 100644
--- a/it.tests/pom.xml
+++ b/it.tests/pom.xml
@@ -9,7 +9,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-it-tests
@@ -86,6 +86,11 @@
14
*.json
+
+
+ org.apache.felix:org.apache.felix.http.jetty12:HIGHEST
+
project.version
diff --git a/it.tests/src/main/features/jetty12.json b/it.tests/src/main/features/jetty12.json
new file mode 100644
index 00000000..966153ac
--- /dev/null
+++ b/it.tests/src/main/features/jetty12.json
@@ -0,0 +1,11 @@
+{
+ // WORKAROUND: Sling Starter 14 ships org.apache.felix.http.jetty12 1.1.8, which
+ // intermittently fails IT startup. Pin 1.2.0 here (the aggregate's artifactsOverrides
+ // takes HIGHEST). Remove this file and the override once we move to Sling Starter 15.
+ "bundles": [
+ {
+ "id": "org.apache.felix:org.apache.felix.http.jetty12:1.2.0",
+ "start-order": "5"
+ }
+ ]
+}
diff --git a/it.tests/src/test/java/be/orbinson/aem/groovy/console/it/GroovyConsoleServiceIT.java b/it.tests/src/test/java/be/orbinson/aem/groovy/console/it/GroovyConsoleServiceIT.java
index 37713d66..9e97fe7e 100644
--- a/it.tests/src/test/java/be/orbinson/aem/groovy/console/it/GroovyConsoleServiceIT.java
+++ b/it.tests/src/test/java/be/orbinson/aem/groovy/console/it/GroovyConsoleServiceIT.java
@@ -20,9 +20,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.*;
class GroovyConsoleServiceIT {
@@ -36,11 +34,60 @@ class GroovyConsoleServiceIT {
@BeforeAll
static void setUp() {
httpClient = HttpClients.createDefault();
+ waitForStableReadiness(300, 15);
+ }
- // Wait for the Sling Starter and the Groovy Console content package to be fully installed
- await().atMost(120, TimeUnit.SECONDS)
- .pollInterval(5, TimeUnit.SECONDS)
- .untilAsserted(() -> assertTrue(isHealthy(), "System not healthy"));
+ /**
+ * Polls the Groovy Console servlet with a no-op script until it has returned 200 OK for a
+ * continuous window. This guards against the Sling Starter package-install refresh cascade
+ * that briefly tears down our bundle: a single OK isn't enough — we need stability before
+ * the test cases start firing requests.
+ */
+ private static void waitForStableReadiness(long overallTimeoutSec, long stabilityWindowSec) {
+ long deadline = System.currentTimeMillis() + overallTimeoutSec * 1000;
+ long stableSince = -1;
+ while (System.currentTimeMillis() < deadline) {
+ boolean ready = isGroovyConsoleReady();
+ long now = System.currentTimeMillis();
+ if (ready) {
+ if (stableSince < 0) {
+ stableSince = now;
+ } else if (now - stableSince >= stabilityWindowSec * 1000) {
+ return;
+ }
+ } else {
+ stableSince = -1;
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ fail("Interrupted while waiting for Groovy Console readiness");
+ }
+ }
+ fail("Groovy Console did not become stable within " + overallTimeoutSec
+ + "s (need " + stabilityWindowSec + "s of continuous OK)");
+ }
+
+ private static boolean isGroovyConsoleReady() {
+ try (CloseableHttpClient client = HttpClients.createDefault()) {
+ HttpPost post = new HttpPost(BASE_URL + "/bin/groovyconsole/post");
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("script", "return 'ready'"));
+ post.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
+ post.addHeader("Authorization", AUTH_HEADER);
+ post.addHeader("Connection", "close");
+ try (CloseableHttpResponse response = client.execute(post)) {
+ if (response.getStatusLine().getStatusCode() != 200) {
+ return false;
+ }
+ String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ JsonObject json = JsonParser.parseString(body).getAsJsonObject();
+ return "ready".equals(json.get("result").getAsString());
+ }
+ } catch (Exception e) {
+ return false;
+ }
}
@AfterAll
@@ -223,20 +270,6 @@ void testXmlFragmentExtensions() throws Exception {
assertEquals("1", response.get("result").getAsString());
}
- private static boolean isHealthy() {
- try {
- HttpGet healthCheck = new HttpGet(BASE_URL + "/system/health.json?tags=systemalive,groovyconsole");
- healthCheck.addHeader("Authorization", AUTH_HEADER);
- try (CloseableHttpResponse response = httpClient.execute(healthCheck)) {
- String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
- JsonObject jsonResponse = JsonParser.parseString(body).getAsJsonObject();
- return "OK".equals(jsonResponse.get("overallResult").getAsString());
- }
- } catch (Exception e) {
- return false;
- }
- }
-
private static JsonObject doGet(String path) throws IOException {
HttpGet get = new HttpGet(BASE_URL + path);
get.addHeader("Authorization", AUTH_HEADER);
diff --git a/pom.xml b/pom.xml
index 217045df..6c6e348c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
be.orbinson.aem
aem-groovy-console
pom
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
AEM Groovy Console
@@ -84,8 +84,8 @@
yyyy-MM-dd'T'HH:mm:ss.SSSZ
- 1.8
- 1.8
+ 11
+ 11
UTF-8
UTF-8
${maven.build.timestamp}
@@ -93,8 +93,8 @@
6.4.0
6.5.10
- 4.0.31
- 9.7
+ 5.0.6
+ 9.9.1
0.9.4
@@ -168,8 +168,8 @@
3.8.1
groovy-eclipse-compiler
- 1.8
- 1.8
+ 11
+ 11
utf-8
diff --git a/ui.apps.aem/pom.xml b/ui.apps.aem/pom.xml
index 7252628e..d5c618de 100644
--- a/ui.apps.aem/pom.xml
+++ b/ui.apps.aem/pom.xml
@@ -6,7 +6,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-ui.apps.aem
diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml
index 43c2a5a8..08902bb8 100644
--- a/ui.apps/pom.xml
+++ b/ui.apps/pom.xml
@@ -6,7 +6,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-ui.apps
diff --git a/ui.config/pom.xml b/ui.config/pom.xml
index fd959fc6..4bec4771 100644
--- a/ui.config/pom.xml
+++ b/ui.config/pom.xml
@@ -5,7 +5,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-ui.config
diff --git a/ui.config/src/main/content/jcr_root/apps/groovyconsole-config/osgiconfig/config/org.apache.felix.hc.generalchecks.ServicesCheck-groovyconsole.config b/ui.config/src/main/content/jcr_root/apps/groovyconsole-config/osgiconfig/config/org.apache.felix.hc.generalchecks.ServicesCheck-groovyconsole.config
deleted file mode 100644
index cbd50cbe..00000000
--- a/ui.config/src/main/content/jcr_root/apps/groovyconsole-config/osgiconfig/config/org.apache.felix.hc.generalchecks.ServicesCheck-groovyconsole.config
+++ /dev/null
@@ -1,3 +0,0 @@
-hc.name="Groovy Console Services"
-hc.tags=["groovyconsole"]
-services.list=["be.orbinson.aem.groovy.console.GroovyConsoleService"]
diff --git a/ui.content/pom.xml b/ui.content/pom.xml
index fc0f019b..fc6213dd 100644
--- a/ui.content/pom.xml
+++ b/ui.content/pom.xml
@@ -6,7 +6,7 @@
be.orbinson.aem
aem-groovy-console
- 19.1.1-SNAPSHOT
+ 20.0.0-SNAPSHOT
aem-groovy-console-ui.content