We reduced tests from hours to just minutes using automatic GlassFish pools

henk531 pts0 comments

The BalusC Code: From hours to minutes: GlassFish pool for Jakarta EE TCKs

skip to main | skip to sidebar

The BalusC Code

Code depot of a Jakarta EE developer

Thursday, June 4, 2026

From hours to minutes: GlassFish pool for Jakarta EE TCKs

Jakarta EE TCKs are notoriously slow.<br>The bulk of the wall clock is not test execution but GlassFish cold-start: every test module unpacks a dist, boots a domain, deploys, undeploys, and stops the domain.<br>With one hundred-ish test modules and several seconds of start/stop per module, a full TCK run easily takes hours.<br>The arquillian-glassfish-server-pool module and the glassfish-pool-maven-plugin, both released as part of OmniFish arquillian-container-glassfish 2.2.0, eliminate that overhead by sharing a pool of pre-started GlassFish instances across the entire reactor.

The proof of concept: Faces TCK

The Jakarta Faces TCK historically consisted of two parts.<br>The "old TCK" was the original Oracle suite: an Ant-driven JavaTest harness inherited from the JSF 1.x days, with around 5000 tests.<br>The "new TCK" was the body of contributed and later-added tests built on JUnit + Arquillian, optionally with HtmlUnit or Selenium for browser interaction.<br>Running both ends to end easily took over 3 hours on Jenkins CI, dominated by the old TCK.

Folding the old TCK into the new-TCK style was always the goal, but per-test manual conversion was prohibitively cumbersome; AI-assisted development is what finally made it feasible.<br>Pull requests #2145, #2146, #2147, #2149, and #2150 mechanically migrated the entire old TCK, with Claude Code doing the bulk of the rewriting and consolidating the remaining HtmlUnit assertions onto Selenium along the way.<br>WAR consolidation (one WAR per feature group instead of one per test) brought wall clock down to ~1h.

The second step was an in-house gf-pool prototype (#2156) that pre-started a pool of GlassFish instances and leased one slot per failsafe-forked JVM.<br>With mvn clean verify -T8 (8 threads), the full Faces TCK reactor now finishes in under 4 minutes .<br>The prototype proved the model works; the natural next step was extracting it into a reusable Maven plugin so other TCKs do not have to copy-paste the wiring.

PhaseLinuxMacBookJenkins<br>Pre-migration02:57 h02:53 h03:19 h<br>Post-migration01:05 h40:52 m01:18 h<br>With gf-pool3:46 m (-T8)4:47 m (-T5)13:06 m (-T2)

Linux: Intel Core i9-10900X with 32GB

MacBook: M1 Pro 10 Core with 16GB

Jenkins: Eclipse Jiro with "2 CPU" and "8 GB"

What gf-pool is

The pool ships in two artifacts:

arquillian-glassfish-server-pool: a runtime Arquillian DeployableContainer that leases a slot for the duration of a test JVM and deploys against the leased slot's DAS through the standard CommonGlassFishManager.

glassfish-pool-maven-plugin: lifecycle goals (up, down, provision, status, nuke) that provision and start slots before integration-test and stop them after.

Provisioning clones a single source GlassFish install into slot-1/, slot-2/, …, rewrites each slot's domain.xml so its ports land in a non-overlapping window (adminBase + (slot - 1) * portStride), and starts every slot in parallel.<br>Each test JVM acquires an exclusive FileChannel.tryLock() on slot-N/lock, reads slot-N/ports.properties, and holds the lock for the JVM's lifetime.<br>The lease protocol is pure Java; there's no -javaagent, no surefire argLine plumbing, and no shell scripts.

The pool grows on demand.<br>A sequential build uses one slot; mvn clean verify -T4 grows to four; -T8 grows to eight.<br>A JVM shutdown hook installed on Maven's own JVM stops every slot at session end (or on Ctrl+C), so no orphaned processes survive a hard build failure.

The optimal -TN for your machine is bounded by available RAM, not by core count.<br>Each slot is a full GlassFish JVM plus a failsafe-forked test JVM, so the dominant cost is heap and resident memory, not CPU. Moreover, if your TCK drives a browser (as Faces does), each slot also spawns its own Chrome plus chromedriver, which pushes the total to ~1.5GB per slot.<br>A 16-core box with 16GB will usually thrash at -T8 while a 8-core box with 32GB happily handles -T8; pick N by watching resident memory and swap, not number of processors.

Maven setup

Two plugin blocks: run the pool plugin (which resolves and unpacks GlassFish itself), and point failsafe at the same and .

ee.omnifish.arquillian<br>glassfish-pool-maven-plugin<br>2.2.0

${project.build.directory}/pool<br>${project.build.directory}/dist/glassfish9

org.glassfish.main.distributions<br>glassfish<br>9.0.0-M2<br>zip

pool-upup<br>pool-downdown

maven-failsafe-plugin

${project.build.directory}/pool<br>${project.build.directory}/dist/glassfish9

The block tells the plugin to resolve the named artifact through your usual Maven repositories and unpack it under ${project.build.directory}/dist before provisioning runs.<br>Staging is idempotent: re-runs fast-exit when the marker file written after a successful unpack is still present.

Add the runtime as a test-scope...

pool glassfish slot test maven plugin

Related Articles