Summary
Package and benchmark AOT uberjars for the Hara CLI and the real gwdb.common.* backend workload. Hara benefits substantially from AOT. The backend improves moderately, but its typed Postgres hydration still analyzes project source at runtime and prevents a cwd-independent standalone jar.
Reproduction scripts
- Foundation Base:
bin/benchmark-hara-uberjar
- greenways-ai/v2 backend:
backend/bin/benchmark-gwdb-common-uberjar
Both scripts:
- derive/build the relevant AOT uberjar;
- compare fresh JVM source-classpath and uberjar execution;
- default to three trials via
TRIALS=3;
- validate functional output/API shape;
- print averages, speedup, reduction, and artifact path.
Set SKIP_BUILD=1 to rerun only the benchmark against an existing artifact.
Hara benchmark
Workload:
java ... hara.uberjar.main emit js '[(+ 1 2 3)]'
Validation: both paths emit exactly 1 + 2 + 3.
Three fresh-JVM trials on 2026-07-22:
| Mode |
Trials |
Average |
| Source classpath |
4.96 s, 4.89 s, 4.89 s |
4.913 s |
| AOT uberjar |
1.49 s, 1.51 s, 1.48 s |
1.493 s |
Result: 3.291x faster, a 69.6% startup reduction.
The Hara build discovers the runtime namespace closure for all supported language specs and AOT-compiles 215 namespaces. The resulting executable artifact is approximately 24 MB and supports languages and emit commands without a Foundation checkout at runtime.
gwdb.common benchmark
Canonical workload: the 40 gwdb.common.* namespaces listed in backend/src/gwbuild/gen/spec/spec_runtime.clj. The benchmark filters that scaffold to common namespaces only.
Semantic validation for both modes:
- 40/40 namespaces loaded;
- all namespaces present;
- 922 public vars exposed.
Three fresh-JVM trials:
| Mode |
Namespace-load average |
Wall-time average |
| Source classpath |
27,219.727 ms |
28.030 s |
| AOT uberjar |
19,518.034 ms |
20.203 s |
Result:
- namespace loading: 1.395x faster, 28.3% reduction;
- end-to-end wall time: 1.387x faster, 27.9% reduction;
- approximately 7.7 seconds saved per cold process.
A separate one-trial script smoke test reproduced the result: 26,948 ms source versus 19,307 ms uberjar (1.396x).
REPL comparison
- First load of the 40 backend namespaces in an already-running project REPL: approximately 21.08 s.
- Requiring the same already-loaded namespaces: approximately 0.09 ms.
- An uberjar helps fresh process startup, but cannot beat keeping a hydrated REPL/JVM alive.
Backend limitation found
The backend uberjar is AOT compiled, but it is not standalone outside the backend project directory.
Launching it from /tmp fails with:
The path is:
- AOT namespace initializers execute
defn.pg/typed hydration hooks.
pg-deftype-hydrate-hook calls app/app-rebuild.
module-typed calls postgres.typed.typed-parse/analyze-namespace.
analyze-namespace calls code.project/project and resolves namespace source files from the checkout.
Consequences:
- compiled classes still perform substantial runtime source analysis;
project.clj and backend source paths must be available relative to the process cwd;
- the runtime analysis/rebuild dominates the remaining ~19.5 seconds.
The uberjar build also reports duplicate resource warnings because backend src appears in both :source-paths and :resource-paths.
Follow-up areas
- Cache or serialize typed namespace analysis during the build.
- Hydrate the runtime registry from packaged data instead of reparsing checkout source.
- Make project/source lookup resource-aware if source fallback remains necessary.
- Add a dedicated backend
:main once the runtime no longer depends on the checkout cwd.
- Add benchmark regression thresholds after results are stable across CI runners.
Summary
Package and benchmark AOT uberjars for the Hara CLI and the real
gwdb.common.*backend workload. Hara benefits substantially from AOT. The backend improves moderately, but its typed Postgres hydration still analyzes project source at runtime and prevents a cwd-independent standalone jar.Reproduction scripts
bin/benchmark-hara-uberjarbackend/bin/benchmark-gwdb-common-uberjarBoth scripts:
TRIALS=3;Set
SKIP_BUILD=1to rerun only the benchmark against an existing artifact.Hara benchmark
Workload:
java ... hara.uberjar.main emit js '[(+ 1 2 3)]'Validation: both paths emit exactly
1 + 2 + 3.Three fresh-JVM trials on 2026-07-22:
Result: 3.291x faster, a 69.6% startup reduction.
The Hara build discovers the runtime namespace closure for all supported language specs and AOT-compiles 215 namespaces. The resulting executable artifact is approximately 24 MB and supports
languagesandemitcommands without a Foundation checkout at runtime.gwdb.common benchmark
Canonical workload: the 40
gwdb.common.*namespaces listed inbackend/src/gwbuild/gen/spec/spec_runtime.clj. The benchmark filters that scaffold to common namespaces only.Semantic validation for both modes:
Three fresh-JVM trials:
Result:
A separate one-trial script smoke test reproduced the result: 26,948 ms source versus 19,307 ms uberjar (1.396x).
REPL comparison
Backend limitation found
The backend uberjar is AOT compiled, but it is not standalone outside the backend project directory.
Launching it from
/tmpfails with:The path is:
defn.pg/typed hydration hooks.pg-deftype-hydrate-hookcallsapp/app-rebuild.module-typedcallspostgres.typed.typed-parse/analyze-namespace.analyze-namespacecallscode.project/projectand resolves namespace source files from the checkout.Consequences:
project.cljand backend source paths must be available relative to the process cwd;The uberjar build also reports duplicate resource warnings because backend
srcappears in both:source-pathsand:resource-paths.Follow-up areas
:mainonce the runtime no longer depends on the checkout cwd.