Upgrading a Gradle project from JDK 8 to 17

Introduction

This is isn't a step-by-step guide on how to upgrade your JDK. It's just a list of some things I tripped over on the way to upgrading my own JDK project. The hope is it might help some people who weren't aware of some of these issues.

Note that the codebase itself did not actually need changing for the new JDK version. Issues with the module system aside, the focus of the Java team on compatibility of the language itself has been nearly flawless as usual. All my pain points were around infrastructure and libraries.

Foolishly, I didn't take notes during the upgrade process, so this is just what I remember and can glean from my Git history. This article won't list everything you need to do, and you won't need to do some of these things I've listed below.

Your mileage will vary.

Gradle/JDK version upgrade

Upgrading Gradle was a bit of a tricky proposition for me. As per the Gradle compatibility matrix  Java 17 is supported only on Gradle version 7.3+. Unfortunately, Gradle 7.0 removed a lot of stuff that has previously been deprecated - so you need to get rid of all deprecation warnings in your codebase before you can upgrade to 7.x. I'd been ignoring the deprecation warnings for years - time to pay the piper.

I recommend a multi-stage approach to upgrading an older Gradle project, whether you actually deploy releases between steps is up to you.
  1. Upgrade your Gradle version  to 6.7.x and make sure your build is working, then upgrade all plugins to latest versions (so that hopefully, they work properly with Gradle 7.x).
  2. Upgrade your JDK version to 11, using the new Toolchain support  to specify the JDK. Make sure all your code and libraries work fine with the Java 9 module system.
  3. Run your build with the Gradle warning mode  flag set to tell you what the specific problems are. Then update your build to get rid of all the warnings.
  4. Upgrade Gradle to the latest 7.x release, then check your build is still working.
  5. Finally, change your toolchain specifications to upgrade to JDK 17 and check your build again.
I got overly ambitious and jumped directly to JDK 15 at step 2, since that was the most recent that Gradle 6.x supports. That was a mistake: JDK 15 is no longer current and was not an LTS  release.

Installing JDK 15 in various places (VM instances, docker containers) was extra work because it is no longer a supported release. Stick to Java 11 in the interim step to avoid unnecessary hassle.

Gradle deprecation warnings you might need to resolve.

  • Get rid of any targetCompatibility specifiers in your build and change over to the Toolchain support .
  • update your dependencies  blocks to use the new implementation, api specifiers, etc.
    This changes is really old - even recently I've been writing new build scripts using the old specifiers. 😳
  • Update your scripts for the various Gradle task properties that have been renamed/moved/deprecated:
    • CopyTask: destinationDirdestinationDirectory
      For some properties, you need to change both the name and also how you specify the task property in your Gradle script. So destinationDir "xxx" becomes destinationDirectory = "xxx".
      Note the = operator.
    • JavaExec: mainmainClass
    • JarTask: archiveNamearchiveFileName
    • ZipTask: archivePatharchiveFile
      You may also have to deal with changing types for some properties. For this property, the type changed from File to a chain of wrapper types. In the below code, the commented out code is old and the uncommented is what you'll need for newer versions of Gradle.
      task buildZip(type: Zip){
        ...
        doLast {
          //  ZipUtil.normaliseZipDates(archivePath.absolutePath)
          ZipUtil.normaliseZipDates(archiveFile.get().asFile.absolutePath)
        }
      }          
      
  • Any custom tasks in your buildSrc subproject may need to have @Input/@Ouput annotations specified, if you weren't doing that before. See managed properties .
    This one can be annoying because Gradle only fails when you run the actual task. So if you have many custom tasks that you don't run often, you may still be finding breakages for a few weeks or months.
    This was particularly nasty for me, since my Gradle tasks are integrated tightly with my automation infrastructure for deployments, backups, etc. So things can break at awkward times.
  • Once you upgrade to Gradle 7.x+, you might see more deprecation warnings - there's going to be more breaking changes in Gradle 8. Might as well tackle them while you're on a roll. 😂

Servlet spec upgrade

Gradle's probably not the only dependency you'll have to deal with that's made breaking API changes across the same range of versions that have varying JDK support. A big one you'll likely have to deal with is the Servlet API change.

The Jakarta EE 9 "big bang" change

With the release of Jakarta EE 9 , the Servlet API has changed to 5.0 which has mandated a new package structure. Any code that previously referred to javax.servlet.* must now use jakarta.servlet.*. This Stack Overflow answer  has a great breakdown of all the Java EE / Jakarta EE shennanigans.

Upgrading Jetty for the JDK 8 to JDK 17 change

The Jetty landing page  has a simple matrix outlining the mapping between Jetty 9/10/11 and what JDK/Servlet version they support.

For focusing on just migrating your JDK, you probably want to start with Jetty 10 to minimise changes.

Spring folks: you don't get a choice - Servlet 5.0 support is not available until Spring 6 .

Infrastructure

When you've finally got your build working and all your tests are passing - you're going to want to consider your infrastructure - deployment, build, etc.

Choosing a JDK distribution

Since the days of Java 8, the landscape around JDK distributions has changed a fair bit. Somewhere along the lifecycle of Java 8, I got comfortable with the AdoptOpenJDK project and transitioned all my projects off the Oracle distribution. Note that the AdoptOpenJDK project has become the Adoptium  project, but the actual JDK distribution has been renamed to Temurin .

For my project, I chose the Amazon Corretto  distribution. All my stuff runs on AWS infrastructure. I run Amazon Linux 2 VM instances and docker containers and I think Amazon will continue to support Corretto for a long time.

It's a pretty personal / project-specific choice that you should research and decide for yourself.

Once you've decided on a JDK distribution, you might want to go back to your build and specify the distribution as well as the version in your Gradle toolchain specification.

Choosing a Docker Image

For some of my old infrastructure on Java 8, I took the easy route and was just using the OpenJDK  docker images directly, relying on the JDK already being installed on the image.

The AdoptOpenJDK 17 docker images are quite different from the 8 series. The default 17 images have changed base operating system to Oracle Linux, and changed the default shell to JShell . Since I wasn't comfortable with those changes, I decided I should make a considered decision about what base container image I should be using too.

In the end, I decided not to use a Java-based docker image at all. I've standardised the containers in my infrastructure on Amazon Linux 2 docker images, since I'm using AL2 for my VM images. I figure it makes sense to use the same OS in both places. Beware that, most package repositories including AL2, only make available supported releases by default. That means the latest 8, 11 and 17 releases are easily installed plus whatever non-LTS release is current (JDK 18 is next ). It'll take extra steps to install an older JDK version (like 15, for example).

Another option many people will want to consider is the official Adoptium docker images .