Skip to content

Upgrade guide

Cédric Champeau edited this page May 23, 2023 · 19 revisions

Upgrading to Micronaut Build 6.x

This version of Micronaut Build is a major breaking version. It is intended to be used starting with Micronaut 4, in particular because it bumps the default Java version to 17, but it is possible to use it on older modules, granted that you change the defaults, following the instructions below.

Which version to choose?

  • For Micronaut 3.x, select the latest 5.x version
  • For Micronaut 4.x, select the latest 6.x version

Release 6.5.x

The 6.5.x release line contains a number of (minor) breaking changes:

  • the plugin is now built with Java 17 and Gradle 8. It will no longer work on builds which require Java 11 to build.
  • there is no longer a default version for Logback. This used to create a conflict with newer Micronaut releases. You have 3 different options to set the logback version (which is used only in tests):
  1. (preferred) set micronaut-logging version in your libs.versions.toml file. This will automatically import the mnLogging catalog and add logback to your test implementation dependencies. For other scopes,you can use the mnLogging.logback.classic dependency notation.
  2. set logbackVersion in your gradle.properties file
  3. set logback in the [versions] section of your libs.versions.toml file

Release 6.4.x

This release supercedes the 6.3.x line. It contains a lot of bugfixes and minor changes. It is no longer maintained.

Release 6.3.x

This release is superceded by the 6.5.x branch. It is no longer maintained.

This release is compatible with Gradle 8. It also provides for an optional breaking change known as "standardized project names". If you upgrade to this version, your project will emit warnings like these:

=========================================================
Standardized project names are disabled.
Consider enabling them with
    micronautBuild.useStandardizedProjectNames=true
in settings.gradle.

Then you will need to replace project dependencies from
   project(':foo')
to
   project(':micronaut-foo')
in your build scripts
=========================================================

Standardized project names is a first step towards simplifying composite build support with Gradle. One of the issues we have today is that project names differ from their GAV coordinates, which makes it non trivial to support composite builds. By enabling standardized project names, the project names will change to match the GAV coordinates, which means that their short name will start with micronaut-. This change is not applicable to test suite projects (projects starting with test-suite-.

As soon as you enable this feature, you will need to rewrite project dependencies from project(':foo') to project(':micronaut-foo').

Release 6.2.x

This release is compatible with Gradle 7.

Release 6.1.x

This release is not maintained anymore, please upgrade to 6.2.x.

Potential breaking changes

Java 17

The build now defaults to Java 17. If you say nothing, the build will target Java 17 features and bytecode level. Builds will also use Java toolchains by default. It is possible to set a different language level for a project by changing the micronautBuild configuration in a project's build.gradle file:

micronautBuild {
   javaVersion = 11 // override the default version to 11
}

Note that if a project was using the sourceCompatibility or targetCompatibility properties, a deprecation warning will be issued. Instead of:

micronautBuild {
    sourceCompatibility = "11"
    targetCompatibility = "11"
}

you should write:

micronautBuild {
   javaVersion = 11
}

Support for Groovy 4

The build now supports building against Groovy 4. Before release 6.0, there was a resolution rule to force the Groovy version of the whole project to the version specified in the build (via a resolutionStrategy rule). This rule is now removed: in case of a conflict, each project needs to deal with the conflict appropriately. The reason to avoid using a rule is that consumers do not inherit from the rule, so it could lead to runtime issues.

Miscellaneous changes

  • build has been updated to remove deprecated Gradle API usages
  • the PDF generation task has been removed
  • builds should be compatible with the configuration cache (starting with Gradle 7.6)
  • the dependency updates plugin has been removed (we use our own version catalog update task)

Upgrading to Micronaut Build 5.3.x

This version introduces 2 potentially breaking changes:

  • a binary breaking change automated test: if a binary incompatible change is discovered, the build will fail, see below how to configure
  • BOM validation is now stricter and will fail by default instead of warning

Binary breaking changes discovery

The check phase now triggers API breaking change verification. If the plugin discovers an API breaking change, it must be accepted explicitly via the config/accepted-api-changes.json file.

In addition, the plugin provides an extension which can be used to configure the location to the accepted changes file, to completely disable validation for a module, or explicitly set the baseline version instead of checking via GitHub releases:

micronautBuild {
    binaryCompatibility {
        enabled = true // this is the default
        acceptedRegressionsFile = file("path/to/another/json.file")
        baselineVersion = "1.0-explicit-version"
    }
}

BOM validation

BOM validation is stricter and will make sure that:

  • all dependencies which appear in a BOM are valid, in the sense that they exist in Maven Central for the specified version
  • all imported BOMs only declare dependencies on the same group as they belong to, unless it's a Micronaut BOM

In some rare cases, it may be necessary to explicitly accept BOMs which break those rules. This can be done via the suppressions block of the micronautBom extension:

micronautBom {
   suppressions {
        dependencies.addAll([
                "io.micronaut.kubernetes:micronaut-micronaut-kubernetes-operator:3.3.0",
                "io.micronaut.oraclecloud:micronaut-oraclecloud-sdk-processor:2.0.4",
                "io.micronaut.data:micronaut-data-document-tck:3.3.0-RC1",
                "io.micronaut.data:micronaut-data-tck:3.3.0-RC1",
                "io.micronaut.serde:micronaut-serde-tck:1.0.0-RC2",
                "io.micronaut.servlet:micronaut-test-kotlin-jetty:3.2.0"
        ])
        bomAuthorizedGroupIds.put("io.r2dbc:r2dbc-bom", ["com.google.cloud", "org.mariadb", "dev.miku"] as Set)
    }
}

The dependencies property lets you accept some dependencies which would have failed validation. The bomAuthorizedGroupIds is used to declare, for a particular non-Micronaut BOM, what group ids it can specify. For example, the io.r2dbc:r2dbc-bom would normally only be allowed to have dependencies on modules living in io.r2dbc. In practice, it's not the case and we have no choice but accepting that it gives recommendations about other groups.

Upgrading to Micronaut Build 5.2.x

⚠️ Please use version 5.2.1 or higher.

The most notable change of Micronaut Build 5.2 is the integration with Sonar Cloud via the SonarQube, JaCoCo and JaCoCo Report Aggregation plugins. It is all handled by 2 new internal plugins:

  • io.micronaut.build.internal.quality-checks:

    • Applied automatically by the common plugin; configures Checkstyle, JaCoCo and Sonar.
  • io.micronaut.build.internal.quality-reporting:

    • To be applied to the root project only; it consumes and aggregates the reports produced by the quality-checks plugin.

Note that Micronaut Build 5.2.x requires Gradle 7.4 or higher.

To use this version, simply add the io.micronaut.build.internal.quality-reporting plugin to the root project:

plugins {
    id "io.micronaut.build.internal.docs"
    id "io.micronaut.build.internal.dependency-updates"
    id "io.micronaut.build.internal.quality-reporting"
}

Upgrading to Micronaut Build 5.1.x

The 5.1 release of Micronaut Build plugin reworks how Javadocs are aggregated and adds support for the remote build cache. It should be straightforward to upgrade from 5.0.x but you should double check that the generated javadocs are ok.

Note that 5.1 also enables forked compilation by default. If, for some reason, memory limits are not appropriate for your project, you can tweak the maximum memory allocated to worker compilers in the project module convention plugin:

micronautBuild {
    compileOptions {
        maxMemory = "512m"
    }
}

In order for the remote cache to be configured correctly, you will also have to upgrade to the latest GitHub workflows templates.

Upgrading to Micronaut Build 5.0.x

The 5.x release of the Micronaut Build plugins provides a number of improvements aimed at making builds faster, cacheable and more maintainable. As a consequence, it is a potential breaking change to upgrade to this release. This page is here to guide you through the upgrade process.

Last update: Micronaut Build 5.0.3

Notable changes include:

  • removal of the cyclic dependency between micronaut-build and micronaut-inject
  • a reworked docs plugin which works in phases instead of tasks overwriting each other outputs
  • a new bom plugin to greatly simplify generation of BOMs

Upgrading should be relatively straightforward if the project doesn't customize docs publication.

We're going to illustrate the changes with the Micronaut AWS project and you can look at the following pull requests for examples of upgrades:

Applying the settings plugin

The first thing to make sure is that you have the settings plugin applied in the settings.gradle file:

pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}

plugins {
    id 'io.micronaut.build.shared.settings' version '5.0.2'
}

then take a look at other build scripts. If you see references to micronaut-build like this, remove the buildscript block as we're going to rely on the plugins block instead:

// Remove this `buildscript` block as it is redundant
buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        classpath "io.micronaut.build.internal:micronaut-gradle-plugins:4.0.1"
    }
}

Avoiding the use of cross-configuration

Cross-configuration is the process of configuring different projects from a single one (typically the root project). This is typically found in the root project, with an allprojects or subprojects block. This pattern used to be popular but has a number of drawbacks:

  • it is difficult to figure out, for a particular project, what plugins it applies
  • multiple allprojects and subprojects blocks can be applied from any build script, making it hard to trace where a change comes from
  • it is inherently unsafe, in particular with regards to parallelism
  • it doesn't properly decouple configuration of projects, leading to performance issues
  • it leads to dirty workarounds like if (project.name == 'docs') which are hard to debug

The new pattern is to use convention plugins. Each project should tell what is is, which is best illustrated by having a plugins block on top of each module:

plugins {
    id 'io.micronaut.build.internal.aws-module'
}

Which basically says "I'm a Micronaut AWS module": not a test suite, not a BOM, but a regular module.

The root build.gradle file should in the end only apply the following plugins:

plugins {
    id "io.micronaut.build.internal.docs"
    id "io.micronaut.build.internal.dependency-updates"
}

The dependency-updates plugin is a legacy plugin which will eventually be replaced with version catalog updates once modules migrate to version catalogs. The docs plugin is here to generate the documentation of a Micronaut project (aggregated javadocs, user manual, ...).

One drawback of getting rid of the allprojects block is that for simple things like the group = "io.micronaut.aws" statement, you'd now have to copy that into every build file. We can avoid that: notice that this io.micronaut.build.aws-module is a project local plugin declared in buildSrc. It's itself composed:

/**
 * Plugin for AWS modules.
 */
plugins {
    id "io.micronaut.build.internal.aws-base"
    id "io.micronaut.build.internal.module"
}

This plugin says that an AWS module is nothing but a regular Micronaut module (something configured with our conventions for checkstyle, publication to Maven Central, ...) as well as another plugin called aws-base. What does that aws-base plugin do? Let's see:

version projectVersion
group "io.micronaut.aws"

This is the plugin which actually sets the version and group of all modules, so that you don't have to repeat yourself. This is the composition over inheritance model.

Note: since 5.0.2 the alternative is actually to set the projectVersion and projectGroup in the root gradle.properties file. The plugin will automatically take care of setting the group and version on all modules.

So we have this "aws module" plugin for libraries, but what about the BOM file? This is where the new BOM plugin comes handy. The Micronaut Build 5.0.0 release comes with a new BOM plugin called io.micronaut.build.internal.bom. It is responsible for generating a BOM file for your multi-project build. By default, it is designed to aggregate all micronaut modules from your multi-project build, and will exclude by convention test modules (test-suite-xxx). Note that if your project uses a version catalog, then this plugin can generate a BOM in a similar way to what the micronaut-core project does (with the convention for managed dependencies).

So, here's what the aws-bom build file looks like now:

plugins {
    id 'io.micronaut.build.internal.aws-base'
    id 'io.micronaut.build.internal.bom'
}

Note that it applies the BOM plugin, but it also applies the aws-base plugin (which will set the group and version, if you remember).

Last but not least, we also have a number of test suites. For now the micronaut-build project doesn't define any particular plugin for declaring a test suite project, but we can do it. This is the io.micronaut.build.internal.aws-test-suite plugin:

/**
 * Plugin for test suite projects.
 */
plugins {
    id 'io.micronaut.build.internal.aws-base'
    id 'java'
}

repositories {
    mavenCentral()
}

This plugin again applies the base plugin, but this time it's a simple java project. We also declare repositories here, so that we don't have to repeat in each build file.

The test-suite project then only needs to tell what it is:

plugins {
    id 'io.micronaut.build.internal.aws-test-suite'
}

dependencies {
...
}

The new documentation plugin

The documentation generation process has been completely reworked in order to make it incremental and cacheable. Before the changes, the docs generation was very messy, with different tasks overwriting each other outputs, and lots of fragile explicit dependsOn declarations so that "things work" which were either redundant or incorrect.

The new process is cleaner and works in stages. If you run the docs task, then you will see in the build/docs directory the result of all previous phases. You must not have a task which touches the contents of the docs directory, but you should have a task which creates intermediate contents instead. The intermediate contents can be seen in the build/working directory:

./build/working/00-api
./build/working/01-includes
./build/working/02-docs-raw
./build/working/03-property-ref
./build/working/04-assembled-docs
./build/working/05-dropdown
  • the 00-api directory contains the generated javadocs.
  • the 01-includes directory contains the individual configurationProperties files from subprojects
  • the 02-docs-raw directory contains the result of the asciidoctor generation for the project
  • the 03-property-ref directory contains the generated property reference file generated from the includes
  • the 04-assembled-docs directory contains the documentation for a single version, assembled with javadocs and configuration reference: this is a usable output which is used for the documentation zip
  • the 05-dropdown directory contains the documentation with the single version replaced with the dropdown version selector

Long story short, if you have a task which used to patch the docs in some way, you should instead create an intermediate directory which takes one or more previous stages as an input, and generates its output in a separate directory. You can take a look at the implementation of the docs plugin for inspiration.

A good way to figure out if your project is doing something wrong is to run the docs task twice in a row. The 2d time, everything should be up-to-date and no task should be executed. If there are tasks executed, best way to figure out what's going on is to generate a build scan (should be automatic if you are registered on ge.micronaut.io, if you're not, ask @melix) which will tell you why things were re-executed.

Sanity checks after upgrading

Verifying publications

It's a good idea to verify what is going to be published after upgrading. Execute:

rm -rf build/repo && ./gradlew pAPTBR

This should generate a Maven repository in build/repo with the artifacts which would be published. Make sure that all the artifacts are in the appropriate group and that all modules which should be published are present.

Verifying docs

Execute:

rm -rf build/docs && ./gradlew docs

Then open the build/docs/index.html page. Check that the dropdown is present, that the javadocs are present as well as configuration property reference.

Upgrading the Micronaut Gradle plugin

Some projects make use of the Micronaut Gradle plugin, the one we provide for end-users, for testing. Starting from Micronaut Gradle Plugin 3.5, the plugins have been refactored in order not to depend from external plugins when not needed. For example, the plugin would previously download the Kotlin plugin even if the user wouldn't use it. In most cases, this should be transparent, but there are cases where the build may fail with ClassNotFoundException, for example:

* What went wrong:
An exception occurred applying plugin request [id: 'io.micronaut.build.internal.docs']
> Failed to apply plugin class 'io.micronaut.build.docs.JavadocAggregatorPlugin'.
   > A problem occurred configuring project ':oraclecloud-bom'.
      > Failed to notify project evaluation listener.
         > org/jetbrains/kotlin/gradle/dsl/KotlinCompile

This problem is caused by a restriction (bug?) in Gradle plugin classloading, happening if we apply the Micronaut Gradle plugin in a convention plugin and that the Kotlin plugins are applied directly in the build.gradle file (because it would be loaded in a classloader which wouldn't have the Kotlin classes, applied in the build file).

The solution is to refactor the build to introduce convention plugins which will apply the Kotlin plugin and the Micronaut Gradle plugin. One can take a look at this PR for an example of refactoring.