Quarkus 3.35 ships three optimizations that, stacked together, change the cold-start budget for serverless workloads on Java: build-time JAR tree-shaking, Profile-Guided Optimization for native builds, and Semeru AOT support.
Each one targets a different layer. Tree-shaking cuts the classes you ship. PGO improves native code quality. Semeru AOT reduces JVM startup time for teams that aren’t going native. The three together cover the cold-start path end to end in a real Java microservice.
Tree-shaking is the one with a real knob to flip today. The new experimental quarkus.package.jar.tree-shake.mode option enables build-time dependency tree-shaking, which means the build prunes classes from your packaged JAR that nothing in your reachable graph actually calls. If you’ve ever looked at a Quarkus uber-jar and wondered why a 200-line REST endpoint pulls 40 MB, this is the lever. Smaller JAR means faster class loading, smaller container image, less to push to the registry, less to pull on cold start.
PGO is the one with the biggest ceiling. Native Image already compiles your app ahead of time, but without profiles the compiler has to guess which branches are hot. PGO runs your app under representative traffic, records the actual hot paths, and recompiles with that information. The serverless-grade numbers people quote from GraalVM blog posts almost always have PGO behind them. Quarkus 3.35 is the version where that workflow becomes practical to adopt, with one caveat worth flagging up front: PGO is a feature of Oracle GraalVM and is not available in GraalVM Community Edition.
Semeru AOT is the path for teams that aren’t going native. You get a real shared classes cache for the JVM, with meaningful cold-start improvement and without touching reflection metadata or wrestling with native-image edge cases.
Three trade-offs worth knowing before you flip the flags:
Tree-shaking is experimental, and the failure mode is reflection. Anything your app reaches via reflection that the static analysis can’t see gets pruned and then explodes at runtime. The first time you flip the mode on, plan to find one or two of those.
PGO doubles your build pipeline. You build once to instrument, run the profiling workload (Quarkus 3.35 uses your integration tests as that workload by default), then build again with the profile. That’s fine for production releases, painful for inner-loop iteration. Most teams underestimate how much CI time the second build costs.
Debuggability on the native side gets worse the more aggressively you optimize. Stack traces from a PGO’d native binary are not the stack traces you’re used to. Have a plan for that before you ship.
The real shift is that an enterprise Java team can now ship serverless workloads with cold-start numbers that fit the budget, while keeping the Java stack, the Java tooling, and the Java hires. 3.35 is the release where that becomes a defensible default for the workload.
If you’re running Quarkus in a function-style workload, the tree-shake knob is the one to try this week. The PGO loop is a sprint-sized investment.
I’m curious to see which direction teams are taking.