Introducing Micronaut Serialization: build-time optimizations for JSON
This is a guest blog post by Graeme Rocher.
The Micronaut® framework is a modern full-stack framework backed by the Micronaut Foundation¹. Today we’re proud to announce a new major contribution by Oracle Labs that further optimizes the construction of Cloud Native applications with GraalVM — Micronaut Serialization. Micronaut Serialization can serialize and deserialize Java types (including Java 17 records) to and from JSON and other formats without using reflection.
Background & Rationale
Since its first release, the Micronaut framework has used Jackson Databind as the default JSON library and it remains the default today. Jackson is a widely adopted and immensely flexible library that has all the bells and whistles necessary to support serializing and deserializing Java types of all shapes and sizes. Jackson however does have several downsides:
· Jackson is fundamentally based on the use of runtime reflection and runtime analysis of annotations. This leads to a large amount of runtime infrastructure that has an impact on startup time and memory consumption.
· With Jackson any type is serializable which results in a larger attack surface area for potential vulnerabilities.
· Jackson’s annotation-based programming model is checked at runtime instead of build time meaning developer errors are caught late.
· Extra Native Image configuration is frequently required for applications that make extensive use of Jackson.
With the release of Micronaut Serialization users now have the choice of an alternative implementation that is largely compatible with existing Jackson annotations but contains many benefits including:
· Micronaut Serialization uses build-time computed bean introspection which completely eliminates the need for reflection. This means there is no extra configuration needed for GraalVM Native Image.
· Annotation use is type checked so a compilation-time error occurs if you, for example, specify an invalid date format in @JsonFormat. This provides a massive productivity boost.
· In terms of security, only types that are explicitly declared as being serializable or deserializable are permitted (although you can import types that you cannot annotate yourself or write custom serializers and deserializers). This greatly increases the security of your application.
· A reduction in runtime code needed for GraalVM Native Image to analyze. Jackson Databind is a 2.1MB JAR file whereas Micronaut Serialization is a mere 400KB because most of the logic exists only at build-time. This also results in a reduction of the native executable size by 3MB.
How it works
Micronaut Serialization interprets source-level annotations (Jackson annotations, JSON-B annotations, or BSON annotations) using Java annotation processing and constructs serializers and/or deserializers at build time that use Bean Introspections eliminating the need for reflection or extensive runtime analysis of annotations.
NOTE: a subset of the available Jackson annotations are supported, however the most common ones are and if you use an annotation that is not supported a compilation error will occur that describes the unsupported annotation.
You can then choose the parser/generator runtime you wish to run Micronaut Serialization on top of, with support included for Jackson Core, JSON-P and BSON in the initial release.
The documentation includes great quick start instructions on how to get started using your favorite annotation-based programming model with an existing Micronaut application.
In general, using Micronaut Serialization is as simple as adding an additional annotation processor dependency and a dependency on the runtime implementation you desire. For example, in a Gradle build to use Jackson core (in other words, only the parser and generator without databind):
Then you can just annotate POJOs or Java 17 records with Jackson annotations:
If you are using the Micronaut framework, Micronaut Serialization will be automatically integrated to handle serialization and deserialization of JSON across the framework from REST responses to messages sent via Kafka. If you want to serialize or deserialize objects programmatically then an equivalent to Jackson’s ObjectMapper interface is also available that you can inject as a dependency.
Up Next: Micronaut Data for Document Databases
We are also hard at work on Micronaut Data MongoDB (which we also plan to integrate with Oracle Autonomous Database’s MongoDB API) and decided to build this implementation of Micronaut Data on-top of Micronaut Serialization. This means that there is no need for multiple JSON parsers/generators (for example, Jackson for the web tier and BSON for the database tier) when building MongoDB applications because Micronaut Serialization integrates with BSON and allows it to be used end-to-end.
We are super excited about how Micronaut Serialization can be used across the ecosystem to build applications that are Native Image ready and follow the philosophy of more intelligent compilers and smaller, lighter runtimes that is the foundation of the Micronaut framework’s design.
¹ Micronaut® is a registered trademark of Object Computing, Inc.