Overview
Axiom is a small JVM framework built to understand what Javalin actually trades off under the hood — not a Javalin replacement, but a controlled experiment across 5 real Maven modules: axiom-core (routing, HTTP context, config, DI), axiom-persistence (HikariCP pooling plus a hand-built transaction API), axiom-persistence-processor (a compile-time annotation processor), axiom-archetype (a Maven project scaffolder), and a thin aggregator module.
The routing DSL reads like Javalin — app.get("/users", ctx -> ...) — backed by a real trie router with :param/wildcard segment parsing, not string-split matching. The one part of Axiom I'd call genuinely solid under scrutiny is axiom-persistence-processor: a real AbstractProcessor using JavaPoet that generates $Tx subclasses wrapping @Transactional methods in Transaction.builder(dataSource)...execute(...) calls — verified with the compile-testing library, which asserts on the actual generated source text, not just that compilation succeeds.
Dependency injection went through a design pivot worth being upfront about — see Key Decisions and Challenges for the full story, including a docs claim that didn't survive the pivot.
Benchmarking against Spring Boot with the same load-testing approach used for NextRush showed a mixed, honest picture: Axiom wins on hello-world and path-param routing, Spring Boot wins on JSON body parsing and query-param routing. A FAIRNESS_VERIFICATION.md in the repo documents catching and fixing an earlier benchmark bug (an unfair 3-middleware-vs-1 setup) before publishing results — the kind of self-correction that matters more than the headline number.
/ Scope
- JVM HTTP server bootstrap (JDK HttpServer-backed)
- Trie-based routing DSL with param/wildcard support
- Reflection-based DI with classpath scanning (@Service, @Repository, @Inject)
- HikariCP-backed persistence layer with a hand-built transaction API
- JavaPoet annotation processor generating compile-time transactional wrappers
- Maven archetype for project scaffolding
Highlights
01
Real compile-time codegen: JavaPoet annotation processor generating transactional wrappers, tested against actual generated source
02
Benchmarked against Spring Boot — wins on routing/hello-world, honest about losing on JSON/query-param scenarios
03
Caught and documented its own unfair benchmark setup before publishing results
04
RFC-first design process across 15 written proposals for the framework's core features
/ Tracks
- Research
- Open source
The Problem
Approach
Key Decisions
- 01
Trie-based routing DSL
app.get("/users", ctx -> ...)reads cleanly, backed by a real trie router with :param/wildcard segment parsing — not naive string-split matching. - 02
DI pivot: Dagger plan abandoned for reflection and classpath scanning
An early RFC proposed compile-time DI via Dagger, explicitly to avoid runtime reflection and classpath scanning. What shipped does the opposite: classpath scanning plus reflective instantiation for @Service/@Repository/@Inject. A legitimate design, just a different one than first planned — worth stating plainly rather than describing the framework as reflection-free. - 03
Compile-time transaction codegen, verified against generated source
axiom-persistence-processor generates a $Tx subclass at compile time for any @Transactional method, wrapping the call in the transaction API. Verified with the compile-testing library, which checks the actual generated source — this is real compile-time codegen, not a runtime proxy. The part of Axiom I'd call genuinely solid under scrutiny. - 04
RFC-first for non-trivial features
Config, validation, error handling, and DI all went through a written RFC before implementation. Most RFCs match their implementation closely; the DI one is the exception, and that mismatch is documented rather than hidden.
Architecture
/ System proof
These are not tool badges. They describe the boundaries, consistency controls, async paths, and failure-mode decisions behind the build.
- 015 Maven modules: core, persistence, persistence-processor, archetype, aggregator
- 02Trie-based routing DSL with param/wildcard support
- 03Reflection-based DI with classpath scanning (@Service, @Repository, @Inject)
- 04JavaPoet annotation processor — compile-time $Tx transactional wrapper generation, verified via compile-testing
- 05HikariCP-backed persistence layer with a hand-built transaction/propagation API
Challenges & How I Solved Them
JVM ergonomics vs TypeScript ergonomics
/ Problem
/ Solution
Benchmark fairness
/ Problem
/ Solution
Docs still describing an abandoned design
/ Problem
/ Solution
Outcomes
- A working annotation processor that generates verifiable compile-time code (JavaPoet + compile-testing), not just a routing toy
- A concrete, scenario-by-scenario performance comparison against Spring Boot instead of an unqualified aggregate claim
- A documented instance of catching and fixing my own unfair benchmark before publishing it
What I Learned
- 01
Rebuilding a framework to study it surfaces the actual trade-offs — reading about Javalin's design doesn't show you why classpath scanning is slower than compile-time codegen the way measuring it does.
- 02
A design can pivot mid-project (Dagger to reflection-based DI) for good reasons — the mistake would be leaving the docs describing the abandoned plan instead of what's actually shipped.
- 03
A benchmark is only as trustworthy as the methodology behind it. Catching my own fairness bug mattered more than the RPS numbers.
Tech Stack
/ Inspired by
- Javalin
Next Steps
- Bring the DI documentation in line with what's actually implemented (reflection-based, not Dagger-based)
- Extend the annotation-processor approach to more of the framework, reducing reliance on runtime reflection
- Repeated-trial benchmark runs against Spring Boot for statistical confidence