Overview
Micronaut (originally from the Grails team at Object Computing) takes the build-time idea to its logical end: dependency injection, AOP and configuration binding are computed entirely by annotation processors at compile time. There is no runtime reflection, no classpath scanning and no runtime proxies — the framework's startup cost is near-constant regardless of codebase size. That design predates and anticipated GraalVM native image, which is why Micronaut apps compile to native binaries with almost no extra configuration.
Key points
- Compile-time DI:
micronaut-inject-javaannotation processor generatesBeanDefinitionclasses at compile time; injection uses JSR-330 annotations (@Inject,@Singleton) — errors surface at compilation, not boot. - Compile-time AOP:
@Around/@Introductionadvice woven by generating subclasses during compilation — no CGLIB, no self-invocation trap of the Spring kind. - Bean introspection:
@Introspectedgenerates reflection-free accessors for serialization, validation and data binding (micronaut-serdereplaces Jackson's reflection use). - Micronaut Data: repository queries computed and validated at compile time — contrast with Hibernate/Spring Data building queries at runtime; JDBC/R2DBC flavors, or JPA via Hibernate when full ORM is needed.
- Declarative HTTP client:
@Client("/users")on an interface generates the implementation at compile time. - Cloud-native built-ins: environment-aware configuration, service discovery (Consul,
Eureka), distributed tracing, and first-class serverless via
micronaut-function-aws(Lambda) and GCP/Azure equivalents. - Positioning: own APIs on a JSR-330/Jakarta lineage rather than MicroProfile — the main philosophical split with Quarkus.
Details
Compile-time machinery
Every injectable bean gets a generated BeanDefinition (and @Introspected types a
BeanIntrospection) written during javac by annotation processors — Kotlin (KSP) and
Groovy are equally supported. Consequences: missing dependencies fail the build; startup
does no scanning (fastest JVM cold start of the big three); memory stays flat; and the
entire framework is native-image-friendly because there is nothing dynamic left to
configure. The cost is compile-time magic: build errors in generated code and processor
configuration replace Spring's runtime stack traces.
AOP without proxies at runtime
@Around advice (e.g. @Cacheable, @Retryable, @Transactional) is implemented by
compile-time-generated intercepted subclasses. Because weaving happens in the compiler,
the advised class is what actually gets instantiated — Spring's proxy/self-invocation
pitfall class largely disappears, though final methods still can't be advised.
Micronaut Data
micronaut-data-jdbc (or -r2dbc) is the signature module: repository interfaces like
Spring Data's, but the SQL is generated and checked at compile time — a typo'd
findByEmial is a compile error, and there is no runtime query-metamodel cost.
micronaut-data-hibernate-jpa slots in Hibernate when entity-graph ORM features are
required. Bean Validation is honored via micronaut-validation (Jakarta Validation
annotations, reflection-free).
HTTP server & client
The server (micronaut-http-server-netty) is Netty-based with optional virtual-thread
event-loop offloading; controllers use Micronaut's own annotations (@Controller,
@Get). The declarative client mirrors the server API and gets load-balancing and service
discovery for free — point @Client(id = "inventory") at a discovered service.
Micronaut vs Quarkus
Both are build-time, GraalVM-first frameworks; the split is philosophical. Quarkus bets on standards (CDI, JAX-RS, MicroProfile — SmallRye implementations) and Red Hat's enterprise channel; Micronaut ships its own coherent APIs with JSR-330 at the core, plus the compile-time Data layer Quarkus has no direct equivalent of (Panache still runs Hibernate at runtime). Team background usually decides: Jakarta EE refugees find Quarkus familiar; Spring refugees find Micronaut's API shapes familiar.
Examples
@Controller("/orders")
public class OrderController {
private final InventoryClient inventory;
OrderController(InventoryClient inventory) { this.inventory = inventory; }
@Get("/{id}")
public Order get(Long id) { ... }
}
@Client(id = "inventory") // implementation generated at compile time
interface InventoryClient {
@Get("/stock/{sku}") int stock(String sku);
}
@JdbcRepository(dialect = Dialect.POSTGRES) // SQL generated & validated at compile time
interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}
Related
- Java application frameworks — the landscape — parent overview and comparison table.
- Quarkus deep dive — the other build-time framework; standards-first where Micronaut is API-first.
- Frameworks × Jakarta EE — who implements what — Micronaut's equivalents column spec by spec.
- GraalVM — the native-image toolchain Micronaut's design anticipates.