When you develop a Micronaut application, you write
.groovy files. Through this article, I refer to those files as source code.
The Java compiler compiles source code into a new document suffixed
.class, coded into Java bytecode. The compiled bytecode is platform-independent. A device capable of running Java can interpret/translate this file into something it can run.
The Micronaut framework generates at build time meta-information about your code to power features such as dependency injection, AOP, or bean introspection. Traditional Java frameworks made those features possible via runtime logic, often made possible via reflection. That runtime logic ended with Java applications being slow to start and resource-hungry. Micronaut framework reflection-free build-time generation approach creates powerful but lean Java applications.
The Micronaut compiler visits the end-user code and generates additional bytecode that sits alongside the user code in the same package structure.
Micronaut Framework uses ASM to generate bytecode.
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built
We consider ASM is a stable solution for bytecode generation. For example, ASM is used by the OpenJDK to generate the lambda call sites.
What artifacts does Micronaut Framework generate as bytecode?
Micronaut Framework generates at build-time:
- Bean definitions to power the Micronaut Framework dependency injection engine.
- Bean Introspections to power features such as reflection-free serialization.
- Proxies to power AOP features.
- Bean Factories. For example, Micronaut Kubernetes and Micronaut Oracle Cloud generate factories for external SDK Clients from an annotation processor.
No user’s bytecode modification
Micronaut Framework does not modify the user’s bytecode.
Your classes are your classes. The Micronaut framework does not transform classes or modify the bytecode generated from the code you write.
Why does Micronaut Framework generate bytecode instead of source code?
- Micronaut Framework supports multiple languages – Kotlin, Java, and Apache Groovy. Source code generation will lead to developers asking us to generate source code files in Kotlin or Groovy instead of Java. That is something we cannot afford to do. By generating bytecode, we stay language neutral.
- You can generate bytecode that is independent of the current JDK version. You can generate bytecode for “future” Java versions.
- You can generate bytecode which wouldn’t be allowed by sources. e.g., generate Java 11 bytecode on a Java 8 runtime.
- By generating bytecode, we decouple the compiler from the annotation processing API. Thus, it avoids a two-step compilation process.
- Moreover, we consider Micronaut Framework’s generated code internal. The generated code is optimized for performance. It is not code that you should modify, alter or experiment with it as a developer.
Source code generation
Micronaut Ahead of Time (AOT)
Micronaut Ahead of Time, a framework that implements ahead-of-time (AOT) optimizations for Micronaut applications and libraries, generates source code instead of bytecode via integrations with the Micronaut Gradle plugin and Micronaut Maven plugin.
For Micronaut Ahead of Time (AOT), we chose source code generation instead of bytecode. For source code generation, we use JavaPoet.
JavaPoet is a Java API for generating .java source files.
Why source code generation instead of bytecode generation in Micronaut AOT?
Source code generation is more straightforward than bytecode generation. Thus, it enabled us to develop Micronaut AOT faster.
Moreover, for public-facing APIs, we prefer to expose source code generation instead of bytecode generation and Micronaut AOT is extensible by design. Users may implement custom AOT optimizers. An AOT optimizer may generate new source files via JavaPoet.
Source code generation in Micronaut CLI commands
Micronaut CLI (Command Line Interface) adds several commands which generate source code inside the user project. For example, you can create a sample controller.
% mn mn> create-controller Bar | Rendered controller to src/main/java/foo/BarController.java | Rendered test to src/test/java/foo/BarControllerTest.java
For this use case, source code generation is a perfect match because we expect/encourage users to modify, alter or experiment with these files. Micronaut CLI uses Rocker Templates for source code generation.
Micronaut Framework is an implementation of an annotation-based programming model. It generates bytecode based on the user’s code, but it never modifies the bytecode of the user’s classes. The framework leans towards source code generation in areas where extensibility/modification is required.