11 June 2020

Tags: gradle java preview

You’ve probably heard about Java providing features like records, multi-line text blocks or sealed types and you’d like to try them.

Those features are called feature previews and it means a couple of things:

  1. the Java team wants you to test them and give feedback. They want honest feedback about how it feels to use them, whether you like them or not. Both ways, feedback is important.

  2. because they are feature previews, you shouldn’t them use in production, but you can play with them for toy projects.

There’s actually an important thing about point 2 if you are a library author: never, ever publish a library which uses feature previews on Maven Central. The reason is that the feature previews leak to consumers: as soon as you start using them, any project depending on your code will also have to enable them. This is not a problem for toy projects, it’s clearly a problem for published libraries. In particular, there are no guarantees that the generated bytecode will be compatible with future Java releases, and there are no guarantees that the feature preview will make it to Java eventually.

The goal of this blog post is not to explain what records or sealed classes are, there’s already a lot of litterature about it. Instead, we’re going to explain how to configure your Gradle build to use feature previews and therefore report issues/bugs to the JDK team.

Configuring your Gradle build

The code in this blog post is available on a GitHub repository.

First, make sure you are using latest Gradle versions. If you use the repository above, the Gradle wrapper will make sure that you do. Gradle 6.5, for example, works perfectly fine with Java 14 and even Java 15.

Imagine that you want to compile the following record:

public record Person(
        String firstName,
        String lastName,
        boolean likeJavaRecords) {

}

And execute this test:

class PersonTest {
    @Test void testRecord() {
        Person cedric = new Person("Cédric", "Champeau", true);
        Person otherCedric = new Person("Cédric", "Champeau", true);
        assertEquals(cedric, otherCedric);
        assertTrue(cedric.likeJavaRecords());
    }
}

Then you need to configure the build to do a couple of things:

First, we need to tell the Java compiler to use feature previews:

tasks.withType<JavaCompile>().configureEach {
    options.compilerArgs.add("--enable-preview")
}

Then we need to tell the test runtime to use feature previews:

tasks.withType<Test>().configureEach {
    useJUnitPlatform()
    jvmArgs("--enable-preview")
}

And that’s it, you can run the build with ./gradlew test.

Configuring Gradle to use a different JDK from its own runtime

Usually, people use the same JDK version for running Gradle, than they use to compile and execute tests. It doesn’t have to be that way, and actually the Gradle team is working on adding support for toolchains to simplify the configuration of builds in this case. In the mean time, if for some reason your Gradle build doesn’t want to start on JDK 15, but it works on 14, then you can easily configure Gradle to fork compilation and execution.

You can for example configure the build to use a JDK found externally by specifying an environment variable like in the example below:

providers.environmentVariable("JDK15")
        .forUseAtConfigurationTime()
        .map(::File)
        .orNull?.let { javaHome ->
            println("Configuring your build to use JDK 15 from $javaHome")
            tasks.withType<Test>().configureEach {
                executable = "${javaHome}/bin/java"
            }

            tasks.withType<JavaCompile>().configureEach {
                options.isFork = true
                options.forkOptions.javaHome = javaHome
                options.compilerArgs.addAll(listOf("--release", "15"))
            }
        }

Now you don’t have any excuse not to try feature previews!

comments powered by Disqus