Kotlin News

Kotlin DSL: What’s New?

In the last few TeamCity releases, we made some improvements to the Kotlin DSL. This is a summary of these enhancements.

Custom path for Kotlin DSL files

TeamCity has always had strict requirements regarding the directory location for Kotlin DSL files. It insisted on saving the files under the .teamcity directory, which had some drawbacks. For instance, directories starting with . are hidden by default on some operating systems. 

It is also not very convenient to have this directory in a repository that stores DSL files only. In such cases, it would be more convenient to place all the files in the root directory instead. This would also simplify working with IntelliJ IDEA, which will be able to see the pom.xml right in the root directory and offer to open it as a Maven project.

All of this is possible in TeamCity since the 2024.07 version. Read more about this feature in our documentation.

For the sake of simplicity in the subsequent sections of this post, we’ll assume that the DSL code is stored in the .teamcity directory.

Simplified code reuse with DSL libraries

If you’re employing the Kotlin DSL in several projects, you’ll notice that you’re using the same code again and again. Wouldn’t it be nice to share it somehow?

Usually, when the need to share code arises, we recommend creating a Java library and publishing it to a Maven repository as a Maven artifact. If your company writes Java code, then these Maven repositories are likely already set up, and the process to publish new artifacts there is already familiar.

Once a library is published to Maven, developers can add a dependency to the DSL pom.xml file. The TeamCity server also understands these dependencies, so when it needs to generate configuration files from this DSL, it will download necessary dependencies as well.

If your company doesn’t have a Maven repository or if it’s too cumbersome to publish something there, TeamCity can act as a repository where you can publish your libraries with standard Maven versioning. Starting with TeamCity 2024.12, the Administration area includes a new page: DSL Libraries. This is where system administrators can manage these libraries.

The uploaded DSL library can then be added as a regular Maven dependency to your pom.xml.

Read more in our documentation.

Changes in DSL files and their impact on build reuse

It’s quite common to store DSL code in the main project repository in the .teamcity directory. When a new branch is created, the DSL files are also branched, allowing for running builds in branches with modified settings. However, when DSL files are stored in the same repository as the main source code, one potentially frustrating drawback emerges. 

Let’s imagine that we have a build chain of two build configurations (Run tests depends on Compile):

Someone then makes a commit to the .teamcity directory, which changes the settings of Run tests only. Since it’s a commit to the main repository, TeamCity computes a new revision for checkout on the agent, so when you run Run tests, it will rebuild Compile too, even though the only changes in the repository were in the DSL files.

Experienced TeamCity users remember that there are checkout rules that allow excluding directories from checkout on the agent, and they would try to add something like this to the Compile build configuration’s checkout rules:

-:.teamcity

This would not work because internally, TeamCity always adds +:.teamcity to ensure that it doesn’t miss a revision that affects the build configuration settings, as it does in this case, so the build should be re-run.

Starting with 2025.07, this no longer happens. Now, if .teamcity is excluded via checkout rules, it won’t be checked out on the agent and the build revision won’t be affected by commits to this directory. This allows reusing Compile builds from our example, where changes in the DSL do not affect their settings. At the same time, if DSL changes affect the build settings, a new build will be started.

So far, so good, but if checkout rules exclude the .teamcity directory, TeamCity will hide any changes from this directory in the builds of this build configuration. This is not ideal, as it can complicate the analysis of the build problems.

Fortunately, to bring back the changes, you just need to enable the Show settings changes in builds option on the versioned settings page:

Read more about all the build reuse conditions in our documentation.

More precise changes of DSL settings in builds

With the Kotlin DSL, it’s not easy to determine whether a change in a .kt or .kts file actually affects a build configuration’s settings. As the DSL can be considered a collection of Kotlin files, there’s no correspondence between a Kotlin file name and a specific build configuration. 

Moreover, the entire project can be generated by a single settings.kts file. Without actually executing the DSL code, how would you understand that changes in a .kt file affect the settings of a particular build configuration?

You may ask why this is important. We’ve already mentioned that if the Show settings changes in builds option is enabled, the builds start showing all the changes made in the DSL on the Changes page, or in the Changes popup. These changes usually have a special icon to distinguish them from other commits:

Given that it is not possible to determine which DSL changes affect a specific build configuration’s settings simply by looking at the file names, TeamCity simply shows all the changes related to the .teamcity directory in all of the project’s build configurations. This can be rather distracting and was often the reason why the Show settings changes in builds option remained disabled.

However, TeamCity 2025.03 introduced a significant improvement in this area. If a commit affects the .teamcity directory, then it will initially be shown as pending in all of the project’s build configurations.

Then, when the actual builds are triggered, and the DSL is executed, TeamCity will perform an additional analysis of the settings produced by the DSL execution. If the settings generated for a build configuration are the same as in previous builds, then the DSL commits will be hidden.

Incremental compilation of the Kotlin DSL (teamcity-configs:generate)

If you’re using the Kotlin DSL, you can run the DSL locally with the following command:

mvn teamcity-configs:generate.

This command executes a task that is provided by the TeamCity Kotlin DSL plugin for Maven. The task will first compile the Kotlin files, then execute them, and finally generate the XML files.

During the compilation phase, the task always recompiles everything, which can be time-consuming, especially if there are thousands of .kt files. Starting with 2025.07, you can enable Kotlin incremental compilation in your pom.xml file:

<project>
  …
  <parent>
    <groupId>org.jetbrains.teamcity</groupId>
    <artifactId>configs-dsl-kotlin-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <properties>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
  </properties>

  <repositories>
    <repository>
…
</project>

The TeamCity Maven plugin will interpret this property and enable incremental Kotlin compilation. If properties are configured correctly, you should see the following output:

[INFO] Generate TeamCity configs in <path to project>/target/generated-configs, format kotlin, dslDir: <path to project>

[INFO] Kotlin incremental compilation enabled, compiler caches directory: <path to project>/target/kotlin-dsl-ic

Our measurements show that incremental compilation can reduce the execution time of this task by as much as 50%!

Adding build caches into the mix

We’ve improved the performance when locally generating TeamCity settings, but if you have a big DSL project where incremental compilation makes a difference, chances are you’re also running the same teamcity-configs:generate on your agents. There is often a validation build that is executed before merging changes to the default branch. 

Enabling incremental compilation can speed up this build, but only if it runs mostly on the same agents. This is because the Kotlin compiler needs caches produced by the previous compilation for incremental compilation to work. If the build runs on different agents, the caches will likely be empty, providing no benefit.

Fortunately, TeamCity comes with handy build caches. If you haven’t heard about this feature, now is a good time to try it out! 

The typical build cache configuration in the DSL for our use case looks like this:

features {

  buildCache {
    name = "DslCompilation-%teamcity.agent.jvm.os.name%"
    rules = """
        target/kotlin-dsl-ic
        .m2
    """.trimIndent()
  }
}

The corresponding Maven build step is defined as:

steps {
  
  maven {
    goals = "teamcity-configs:generate"
    runnerArgs = """-Dmaven.repo.local=%teamcity.build.checkoutDir%/.m2"""
    localRepoScope = MavenBuildStep.RepositoryScope.MAVEN_DEFAULT
  }
}

Note that the cache name includes %teamcity.agent.jvm.os.name%. This allows using different caches for agents on different operating systems, which is important because the Kotlin compiler stores some OS specific information in the cache. Caches produced by the Windows agent won’t be used by the Kotlin compiler running on Linux. 

The cache also includes the .m2 directory, allowing you to avoid re-downloading Maven dependencies again during the build.

Due to additional overhead, like source code checkout or cache download, achieving a 50% performance boost with build caches on a freshly started TeamCity agent can be challenging. However, it’s still possible to experience a 20–30% improvement, which can be significant for large projects.

Conclusion

For your convenience, here’s a summary of the features discussed in this blog post alongside the corresponding TeamCity version in which they were introduced:

Custom path to Kotlin DSL files2024.07
DSL libraries2024.12
More precise changes in builds2025.03
Better reuse of builds2025.07
Incremental Kotlin compilation for teamcity-configs:generate2025.07
Build caches2023.11

We hope that these features improve your day-to-day work as a Kotlin DSL developer. We look forward to your feedback, especially if you encounter any difficulties. Please share your experience in the comments below!

As always, happy building!

image description