Is it possible to build with Gradle using old Kotlin version?

I have a project A with Gradle 8.12.1 and a project B with Gradle 8.8. Project B using dependecies from project A. Is it possible to build project A with Gradle 8.12.1 using Kotlin version 1.9.24?
When I try to compile project B I get en error:

Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin. The actual metadata version is 2.1.0, but the compiler version 1.9.0 can read versions up to 2.0.0.

I’ve tried to specify compiler options in build.gradle.kts of Project A:

kotlin {
    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
    }
}

That’s not really Gradle question, but a Kotlin question.
The Gradle version is irrelevant for this.
You have the Kotlin stdlib version 2.1.0 in your dependencies, but try to use the Kotlin compiler version 1.9.0.
But the Kotlin compiler can only read classes produced by one minor version in the future, not two like in your case.

./gradlew buildEnvironment shows:

+--- org.jetbrains.kotlin:kotlin-stdlib:{strictly 2.0.21} -> 2.0.21 (c)

How can I find where this constraint was specified?

Easiest is, using a build --scan.
But you can also just look higher in the buildEnvironment output what is the parent of that line, there the constraint is coming from.
But actually that is the build script classpath, not your compile classpath.
And it is Kotlin 2.0 which could be compiled by 1.9.

There are following lines in every classpath

+--- org.jetbrains.kotlin:kotlin-stdlib:{strictly 2.0.21} -> 2.0.21 (c)
\--- org.jetbrains:annotations:{strictly 13.0} -> 13.0 (c)

I’ve tried in root build.gradle.kts

configurations.all {
    dependencies {
        api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24!!")
        api("org.jetbrains.kotlin:kotlin-stdlib:1.9.24!!")
        api("org.jetbrains.kotlin:kotlin-reflect:1.9.24!!")
    }
    resolutionStrategy {
        eachDependency {
            if (requested.group == "org.jetbrains.kotlin" && requested.name == "kotlin-stdlib") {
                println("== $requested")
                useVersion("1.9.24")
            }
            if (requested.group == "org.jetbrains.kotlin" && requested.name == "kotlin-reflect") {
                println("== $requested")
                useVersion("1.9.24")
            }
        }
        force("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
    }
    resolutionStrategy.capabilitiesResolution.withCapability("org.jetbrains.kotlin:kotlin-stdlib") {
        select("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
    }
}

But when I try to compile a project B I still get an error:

Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin. The actual metadata version is 2.1.0, but the compiler version 1.9.0 can read versions up to 2.0.0.
The class is loaded from /home/******/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/2.1.0/85f8b81009cda5890e54ba67d64b5e599c645020/kotlin-stdlib-2.1.0.jar!/kotlin/Unit.class

But I’m confused with 2.0.1, why not 2.0.21…

But I’m confused with 2.0.1, why not 2.0.21…

No I’m confused, you nowhere mention 2.0.1.
Besides that, hard to say without seeing the full build or at least a build --scan URL.

Do you maybe use the Spring Dependency Management plugin?
If so, that is a relict from times when Gradle did not have built-in BOM support, by now does more harm than good, and even its maintainer recommends not to use it anymore but the built-in BOM support using platform(...).
That plugin often causes strange non-discoverable version enforcements being in place for example.

Yes, the problem comes from a custom plugin. It adds api org.jetbrains.kotlin:kotlin-stdlib with project.dependencies.add(String, Object) without versions. But suddenly org.jetbrains.kotlin:kotlin-stdlib gets constraint with a version 2.1.0.
What mechanism does Gradle use to determine versions for dependencies added by plugins?

Whether they are added by a plugin or build script is irrelevant, they are always resolved the same.

I tried to add org.jetbrains.kotlin:kotlin-stdlib:1.9.24 in this custom plugin and the version wasn’t changed to 2.1.0. Currently, I am looking for a solution without changing this plugin…

Is it possible to ignore a constraint and use the strict version of the dependency?

That sounds very strange and I cannot really answer without an MCVE to look at.

Actually you should usually never add the Kotlin stdlib, as the Kotlin Gradle Plugin is already automatically adding it for you if you did not add it manually.
Maybe by adding the version there the KGP is happy with the dependency already being there and does not add its own one, while without version it adds it or similar.

But even then strict versions, resolution strategies, and forced versions should usually all individually help already.
From the given information I’m not able to see what is causing this. :frowning:

I made a test project and I managed to build it with old version of Kotlin with KGP compiler options.

kotlin {
    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
        apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
    }
}

But what do I need to do in my multiproject so that the compiler options apply to all subprojects?
If a subproject has its own compiler options block, will it be overridden or merged with the root project’s compiler options?

Even if a subproject does not have any compiler options, it should not use the root projects compiler options.
The root project is the root project, the subproject is the subproject.
If you want to do something in multiple projects, you should create a convention plugin, for example in buildSrc or an included build (I prefer the latter), for example implemented as precompiled script plugin.
This plugin you can then apply to the projects where you want this convention to be effective.

(Even if you find ways online to do it like allprojects { ... } and friends, don’t use them, it is highly discouraged bad practice to do cross-project configuration)

1 Like

If I use a convention plugin, do I need to add it to the plugin block of each subproject’s build.gradle.kts?
If so, It doesn’t suit me, because subprojects consist of generated code and I need a temporary solution with less code that will be deleted in the future.
What are the downsides and pitfalls of adding this to root build.gradle.kts (temporary solution)?

subprojects {
    pluginManager.withPlugin("jvm") {
        configure<KotlinJvmProjectExtension> {
            this.compilerOptions.apply {
                languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
                apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
            }
        }
    }
}

If I use a convention plugin, do I need to add it to the plugin block of each subproject’s build.gradle.kts?

Yes

What are the downsides and pitfalls of adding this

Besides that you use the discouraged unqualified plugin ID of that plugin, any form of cross-project configuration immediately introduces project coupling which works against some more sophisticated Gradle features and optimizations, and usually produces much harder to understand and less maintainable builds.

1 Like

I made a mistake for a plugin ID.

build.gradle.kts with correct ID

subprojects {
    pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
        configure<KotlinJvmProjectExtension> {
            compilerOptions {
                languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
                apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
            }
        }
    }
}

But I realized that I didn’t solve the problem )
Adding compiler options change @Metadata mv property of class Kotlin version:

import kotlin.Metadata;

@Metadata(
   mv = {1, 9, 0},
   k = 1,
   xi = 48,
   d1 = {"..."},
   d2 = {"..."}
)
public class Application {
}

But dependency in pom file of published to Maven local repository artifact still contains kotlin-stdlib 2.1.0

  <modelVersion>4.0.0</modelVersion>
  <groupId>*****</groupId>
  <artifactId>*****</artifactId>
  <version>1.22.127-SNAPSHOT</version>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib</artifactId>
        <version>2.1.0</version>
      </dependency>

In order to get rid of 2.1.0 I created buildSrc with build.gradle.kts:

plugins {
    kotlin("jvm")
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24")
}

As a result I got pom.xml with kotlin-stdlib with 1.9.4

  <modelVersion>4.0.0</modelVersion>
  <groupId>****</groupId>
  <artifactId>*****</artifactId>
  <version>1.22.127-SNAPSHOT</version>
  <dependencyManagement>
    *********
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-stdlib</artifactId>
      <version>1.9.24</version>
      <scope>compile</scope>
    </dependency>
       ****

But when I try to compile an another application with this artifact I get an exception:

Unresolved reference: <class_name>

And I can navigate to this class in IDE and it has a correct (1.9.24) kotlin version in class metadata.
What is the reason the Gradle 8.8 can’t resolve classes from this module?

That’s impossible to say without a full picture, can you show an MCVE?