Search code examples
javagradlegradle-kotlin-dslmulti-release-jar

Code for different Java versions in same Gradle Project


Is it possible to create a Gradle Project in a way so that the major part can be used with one Java version (e.g. Java 11) while some parts rely on a higher Java version (e.g. Java 17) and can only be used by clients with Java 17?

It should be possible to use dependencies compiled for Java 17 in Java-17-parts. The part of the project that is compiled for Java 11 should also be usable from the Java 17 code.

It should also be possible to import the project in the exact same way into other projects and use the Java 17 parts of the project only if they use Java 17 by themselves.


Solution

  • Multi-Release JARs can be used for this. Multi-Release JARs allow to create classes that are loaded only in specific Java versions.

    You can do that by specifying a source set for Java 17 and keeping your main source set for Java 11. You make the Java 17 source set to use be able to access Java 11 code as well.

    Furthermore, you specify a task for compiling Java 17 code with a custom source and target compatibility and the source set for Java 17.

    Normal (Java 11) source code goes to src/main/java will Java 17 source code goes to src/java17/java.

    When creating a JAR, the Java 17 build output is moved to META-INF/versions/16 making it available for Java 16+ only.

    Using the Kotlin DSL, your build.gradle.kts could look like this:

    plugins {
        id("java")
    }
    
    group = "org.example"
    version = "1.0-SNAPSHOT"
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        //Java 11 dependencies here (available for all code)
    }
    tasks {
        jar {//configure JAR creation
            dependsOn("compileJava17")//requires Java 17 code to be built
            into("META-INF/versions/17") {//move Java 17 code into META-INF/versions/17
                from(sourceSets["java17"].output)//take it from Java 17 output folder
            }
            manifest {
                attributes("Multi-Release" to "true")//configure it to be recognized as a Multi-Release JAR
            }
        }
    
    }
    sourceSets.create("java17") {//create Java 17 source set
        this.java.srcDir("src/java17/java")//where to put sources
        dependencies {
            //Java 17 dependencies here (only available for Java 17 code)
        }
        //include Java 11 source code (make it accessible from Java 17)
        compileClasspath += sourceSets["main"].output
        runtimeClasspath += sourceSets["main"].output
    }
    java {
        //configure project to use Java 11
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
        sourceSets["main"]//use main source set
    }
    task<JavaCompile>("compileJava17") {//create task for Java 17
        dependsOn("compileJava")//requires normal Java compilation to be finished
        //set source and target compatibility
        sourceCompatibility = JavaVersion.VERSION_17.name
        targetCompatibility = JavaVersion.VERSION_17.name
        sourceSets["java17"]//use Java 17 source set
    }