Are you curious what new features Micronaut version 3 brings and do you want to know how to upgrade your applications? In this post we will do the upgrade to Micronaut 3 for an existing application and show you how.

For a complete overview of all the new features take a look at the Micronaut website here.

We will start with an existing Micronaut 2 application from this previous post on reflection in native images. We will encounter various new features while upgrading but the list will not be exchausting.

Upgrade dependencies

We are using Gradle in this project so we update the gradle.properties with the version of Micronaut we want.

micronautVersion=3.0.2

A build of the project shows the first breaking change:

/Users/sboland/dev/kung-fu-api/src/main/java/nl/sybrenbolandit/kungfu/kick/KickService.java:3: error: package javax.inject does not exist
import javax.inject.Singleton;

Dependency injection

As we see, the javax.inject package is not part of Micronaut anymore. This is do to trademark restrictions on all javax.* namespaces. More on that can be read at this blogpost.

Fortunately we can leave all annotations the same and only change the packages to jackarta.inject.

Schermafbeelding 2021 10 09 om 12.25.51 1024x169 - UPGRADE TO MICRONAUT 3

Now the build succeeds. But wait, there’s more!

In Micronaut 3 we can qualify injection on types based on its generic arguments i.e. Car<V4> and Car<V8> are different types for the injection mechanism. Previously we needed custom qualifier interfaces to distinguish these types.

It is also possible to determine that a bean cannot be looked up by its type but rather by its supertype. In the following snippet the bean can only be injected by its interface Person.

@Bean(typed = Person.class)
class Student implements Person {

}

Reactive streams

Micronaut 2 came with a transitive dependency on RxJava and many internal features used RxJava too. This made the choice of a reactive streams implementation for your own applications rather easy. With Micronaut 3 all internal usages are switched to project-reactor so RxJava is not included anymore.

If you really want to keep using RxJava2 you can add this dependency io.micronaut.rxjava2:micronaut-rxjava2. But when refactor to project-reactor we keep our application smaller (projet-reactor runtime classes are already present on the classpath) and we do not have to convert from/to RxJava object when dealing with Micronaut’s streams.

So let’s change our simple API to use project-reactor. We first add the dependency.

dependencies {
    ...
    implementation("io.projectreactor:reactor-core")
}

Then we replace Observable with Flux and we replace Single with Mono. These are almost interchangeable but a Flux is equivalent to an Observable with backpressure (you can specify the backpressure strategy). We are only using Single which we can change for Mono.

@Get("/random")
public Mono<Kick> randomKick() {
    return Mono.just(kickService.randomKick());
}

A full migration guide is out of scope for this post. One useful article can be found on medium here.

Introspection and reflection

In this previous discussion on reflection with GraalVM I used introspection and reflection interchangeably. Prior to the Micronaut 3 upgrade this was valid for we saw that Micronaut generated reflection configuration for classes annotated with @Introspected.

With Micronaut 3 these concepts are separated. In most usecases we do not need reflection access but only introspection. So Micronaut 3 will not generate reflection configuration (you can still do this with an additional @ReflectiveAccess annotation). In our example we need conversion to json using Jackson, this only requires introspection.

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Kick {
    ...
}

When we now build the native image: (upgrade the io.micronaut.application plugin to version 2.0.6 first, or get a confusing error about the class javax.inject.Provider)

./gradlew nativeImage

We see that the reflection configuration i.e. the file build/classes/java/main/META-INF/native-image/reflect-config.json is not generated and the application still works fine.

Schermafbeelding 2021 10 14 om 11.21.28 1024x290 - UPGRADE TO MICRONAUT 3

Upgrade tools

There is also a tool to make your life upgrading easier. OpenRewrite has a Gradle (and Maven) plugin to refactor your code. Add the plugin, dependency and task as follows:

plugins {
    ...
    id("org.openrewrite.rewrite") version "5.12.0"
}

dependencies {
    ...
    rewrite("org.openrewrite.recipe:rewrite-micronaut:1.3.0")
}

rewrite {
    activeRecipe("org.openrewrite.java.micronaut.Micronaut2to3Migration")
}

...

Now we can do a dryrun to see what will be changed. The result can be found in build/reports/rewrite/rewrite.patch.

./gradlew rewriteDryRun

If you are content with the changes you can apply them using this.

./gradlew rewriteRun

The changes it made to our application can be found on this branch here. We see that only the dependency injection package is changed. This is not the only change we would want but the only one necessary to make the application build.

Hopefully you are now able to get started with the upgrade to Micronaut 3 for your own applications. Happy upgrading!