GraalVM Native Image & Docker Layering
This page covers two packaging concerns for Juneau applications: Docker image layering (a concrete recommendation you can apply today) and GraalVM native-image support (a feasibility assessment with a forward plan).
Docker image layering
Juneau itself ships libraries, not runnable applications, so there is no Juneau-side fat-jar to layer. Layering happens in your deployable application image. Two practical levers:
- Spring Boot apps —
spring-boot-maven-plugin'srepackagegoal produces a layered jar by default (since Spring Boot 2.4), separating slow-changing dependency layers from your fast-changing application classes. Combined with a multi-stage Dockerfile usingjava -Djarmode=tools -jar app.jar extract --layers, Docker caches the dependency layer across rebuilds. The Juneau Spring Boot petstore sample is built this way. - Embedded-Jetty / plain-jar apps — order your Dockerfile
COPYsteps coarsest-changing first: copy the dependency jars (which include the stable Juneau artifacts) in an earlier layer than your application jar, so a code-only change re-uploads only the small application layer. The starter projects on theapache/juneaumicroservice-*-starterbranches ship minimal Dockerfiles; a production image can apply the layeredCOPYshape described above.
Depending on Juneau through the version-aligned BOM and curated bundles keeps the dependency layer stable and reproducible across rebuilds, which is what makes the cache hit reliable.
GraalVM native-image — feasibility verdict
Juneau's marshalling/bean core is reflection-heavy, so native-image (closed-world AOT) support is non-trivial. A feasibility assessment was performed against the reflective source; the verdict is CONDITIONAL GO.
Why "conditional go", not "no-go"
All of Juneau's reflection flows through a single wrapper layer — org.apache.juneau.commons.reflect.* (ClassInfo / MethodInfo / FieldInfo / ConstructorInfo) with one setAccessible() choke point in AccessibleInfo — and there is no sun.misc.Unsafe and no bytecode generation anywhere in main source. The blockers are the standard native-image reflection blockers, not anything pathological. Critically, the same MarshallingContext / BeanMeta / ClassMeta model that drives marshalling already computes exactly the constructor + field + accessor set that reachability metadata needs — so a Juneau-authored metadata emitter is realistic. That is what tips the verdict from no-go to conditional-go.
The hard blockers (all standard, all addressable)
- Open-world bean reflection — every marshalled bean's full member set must be registered; application bean types are unknowable at Juneau-build time. There is no "zero-config native Juneau" — consumers always need a generation step.
- User-supplied dynamic-proxy interface sets (
@Remoteclients, interface beans) — proxy metadata can't be derived from Juneau's bytecode; it comes from the GraalVM tracing agent or explicit declaration. _typedictionary class resolution viaClass.forName— classes reachable only through a serialized_typestring are invisible to static analysis and must be registered by name.- Reflective swap/converter/DI instantiation through
BeanInstantiator. - Classpath resource + NLS
ResourceBundleloading — broad but low-risk (regex resource includes cover it).
SPIs / ServiceLoader usage, despite intuition, is the easiest category — finite and statically known.
Recommended path (committed follow-on)
The maintainer-approved direction is option (b): a native-image-ready juneau-marshall core (JSON/XML/HTML/UON + swaps + dictionaries), driven by a Juneau-authored reachability-metadata emitter built on BeanContext introspection, with the GraalVM tracing agent covering the statically-invisible parts (@Remote/interface-bean proxies, _type dictionary classes). Target the modern unified reachability-metadata.json format, delivered as a Maven plugin goal / module the consumer points at their bean classes (not checked-in static metadata, which silently drifts).
The REST server (embedded Jetty/Tomcat, ServletContainerInitializer, transport providers) is deferred — most of its residual native-image risk lives in third-party dependencies, not Juneau's own code.
Rough effort: a minimal single-bean JSON round-trip to native is S (~1–3 days); a generally-usable native marshalling core is L (~3–6 weeks); the full framework including @Remote proxies + REST server is XL and out of scope.
This assessment is a feasibility verdict, not a shipped feature. The native-image emitter is tracked as a dedicated committed follow-on; native-image builds of Juneau are not yet supported.