Kotlin + Maven toolchain

The main idea of ​​the article is to show how to make THIS (kotlin & maven toolchain) work together. There will not be a detailed description of the Maven toolchain here, I don’t want to deal with banal translation of manuals.

I'll start with foreplay. As a Kotlinist, I’m somehow on the side of new versions of java, but then JDK 22 arrived – panama/foreign came out of the incubator. For those who are not in the know, this feature gives you the ability to call native code from a 'dynamic library' dll/so (directly from java code). Now you can call system functions yourself, rather than connecting libraries unknown to you.

Briefly about Maven toolchain.

This feature allows you to connect the desired version of jdk (or other tools) automatically. Until April 2024, the maven toolchain plugin was quite weak (compared to gradle toolchains) – it only allowed you to select toolchain/jdk from

But recently (in April 2024) a new version arrived that supports

  • $home/.m2/toolchains.xml file

  • can pick up the current JDK ($JAVA_HOME) if it meets the given criteria

  • searches standard directories (for example, C:/Program Files/…)

    • A really cool thing, you don’t need to do anything at all. I checked it on Windows and it really works. I have no idea what the standard location of the toad is in Linux, but you can at least enter different JAVA_XXX_HOME (this is already good).

  • searches environment variables by pattern (for example: JAVA11_HOME, JAVA22_HOME). The pattern is configurable.

    • By the way, gradle toolchains also support this approach, but as far as I know, the variables need to be added manually to the project file.

  • custom toolchains

Now maven toolchain is even a little better than its brother from gradle. There is an unpleasant bug in gradle that ignores vendor (and other attributes) from $home/.m2/toolchains.xml, as a result it is impossible to distinguish Oracle (standard) JDK from Oracle Graal JDK.

Let's get to the main point.

We still have one problem – the maven kotlin plugin is not compatible with the maven toolchain plugin. At least I didn’t find how to tell him to make friends.

But… the maven kotlin plugin has a configuration parameter jdkHome, which maps to the maven property “toolchain.jdk.version”. This will be our salvation – we need to take the JDK home found by the toolchain plugin and set it to the appropriate property. As for me, the solution + is reliable (all risky code is placed in try/catch), but it is your choice whether to use it in production, or only in a home project. In the worst case, it simply won't work and you'll just go back to the good old JAVA_HOME setting.

<project>
    ...
    <properties>
        <!-- Required JDK version in maven-artifact format. -->
        <toolchain.jdk.version>[22,)</toolchain.jdk.version>

        <!-- Required java-version in numeric format. -->
        <java.version>22</java.version>
        <java.release>${java.version}</java.release>
        <kotlin.version>1.7.0</kotlin.version>
        <kotlin.compiler.languageVersion>1.7</kotlin.compiler.languageVersion>

        <kotlin.compiler.jvmTarget>15</kotlin.compiler.jvmTarget>
        <kotlin.compiler.incremental>true</kotlin.compiler.incremental>

        <kotlin-maven-plugin.version>${kotlin.version}</kotlin-maven-plugin.version>
    </properties>

    <build>
        <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-toolchains-plugin</artifactId>
              <version>3.2.0</version>
              <executions>
                <execution>
                  <goals>
                    <goal>select-jdk-toolchain</goal>
                  </goals>
                </execution>
              </executions>
              <configuration>
                <version>${toolchain.jdk.version}</version>
                <!-- Optional old config. Let's keep it to avoid IDEA warning. -->
                <toolchains>
                  <jdk>
                    <version>${toolchain.jdk.version}</version>
                  </jdk>
                </toolchains>
              </configuration>
            </plugin>
            
            <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>build-helper-maven-plugin</artifactId>
              <version>3.4.0</version>
              <executions>
                <execution>
                  <id>use-maven-toolchain-jdk-for-kotlin</id>
                  <goals>
                    <goal>bsh-property</goal>
                  </goals>
                  <configuration>
                    <source><![CDATA[
                      import org.apache.maven.plugin.descriptor.PluginDescriptor;
            
                      String toolChainJdk = null;
            
                      try {
                        PluginDescriptor pd = new PluginDescriptor();
                        pd.setGroupId("org.apache.maven.plugins");
                        pd.setArtifactId("maven-toolchains-plugin");
            
                        Map pluginContext = session.getPluginContext(pd, project);
                        if (pluginContext == null)
                          throw new IllegalStateException("maven-toolchains-plugin plugin context is not found. Probably it is not set up.");
            
                        // A bit risky part of code: I'm not 100% sure that this class will be used in the future.
                        // It is not specified in the org.apache.maven.model.ConfigurationContainer
                        // (there is just java.lang.Object, but it must be some DOM)
            
                        Object toolchainPluginContext = pluginContext.get("toolchain-jdk");
                        if (toolchainPluginContext != null) {
                          // We can use type Object in bean-shell script instead of Xpp3Dom.
                          // Real type is XML DOM object (currently type is org.codehaus.plexus.util.xml.Xpp3Dom class)
            
                          Object config = toolchainPluginContext.getConfiguration();
                          if (config != null && config.getChildCount() > 0)
                            toolChainJdk = config.getChild(0).getValue().trim();
                        }
                      } catch (Exception ex) {
                        log.error("toolchain-jdk is not found. " + ex.getMessage(), ex);
            
                        // Or we can rethrow error just there.
                        // throw new IllegalStateException("toolchain-jdk is not found.", ex);
                      }
            
                      String requiredJdkVersion = project.getProperties().getProperty("toolchain.jdk.version");
                      if (toolChainJdk != null && !toolChainJdk.isEmpty()) {
                        project.getProperties().setProperty("kotlin.compiler.jdkHome", toolChainJdk);
            
                        log.info("toolchain-jdk for version '" + requiredJdkVersion + "' is " + toolChainJdk);
                        log.info("It will be used for kotlin compiler");
                      }
                      else {
                        String currentJavaHome = System.getProperty("java.home");
            
                        log.info("toolchain-jdk for version '" + requiredJdkVersion + "' is not found.");
                        log.info("  Possible reasons");
                        log.info("    * default java_home matches required java version ");
                        log.info("    * maven-toolchains-plugin is not configured properly");
                        log.info("  ");
                        log.info("Default " + currentJavaHome + " will be used.");
                      }
            
                      ]]>
                    </source>
                  </configuration>
                </execution>
              </executions>
            </plugin>
        
        </plugins>
    </build>
</project>

Full sources at github

useful links

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *