Kotlin String Concatenation: Efficient and Idiomatic Approaches for Modern JVM Development

The most efficient way to perform kotlin string concatenation is using string templates ("$var") or the + operator, which the compiler optimizes to invokedynamic instructions on JVM 9+ or efficient StringBuilder chains on older JVMs, while buildString is reserved for complex iterative constructions.

Modern Kotlin development offers multiple strategies for building strings, but the JetBrains/kotlin compiler automatically selects the optimal bytecode strategy based on your JVM target. Understanding how the compiler transforms your source code ensures you write idiomatic Kotlin without sacrificing performance.

How the Compiler Optimizes String Concatenation

The Kotlin compiler does not naively translate every + operation or template into a new StringBuilder instance. Instead, it employs specific IR lowerings and bytecode generators to produce the fastest possible output.

The FlattenStringConcatenationLowering Pass

In compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/FlattenStringConcatenationLowering.kt, the compiler runs the FlattenStringConcatenationLowering transformation. This pass collapses nested + operations into a single IR node (IrStringConcatenation).

By flattening the concatenation tree, the backend can emit either an invokedynamic call or a compact StringBuilder sequence as a single operation, eliminating the intermediate string objects that plague naive concatenation.

StringConcatGenerator and Bytecode Strategy

The compiler/backend/src/org/jetbrains/kotlin/codegen/StringConcatGenerator.kt file implements the actual bytecode generation. For JVM 9+ targets, it generates invokedynamic instructions targeting StringConcatFactory.makeConcatWithConstants, using placeholder symbols (\u0001, \u0002) to build a recipe string.

For JVM 8 and below, it emits a single StringBuilder instance with chained .append() calls. This dual-path approach ensures optimal performance across JVM versions without requiring source code changes.

Idiomatic Kotlin String Concatenation Patterns

Choosing the right syntax depends on the complexity of your string construction and whether the structure is known at compile time or runtime.

String Templates for Simple Interpolation

String templates are the default choice for most kotlin string concatenation tasks. They are concise, readable, and receive full compiler optimization.

fun greet(name: String, age: Int): String {
    return "Hello, $name! You are $age years old."
}

On JVM 9+, this compiles to an invokedynamic call to makeConcatWithConstants. On older JVMs, it becomes a single StringBuilder chain. The compiler constant-folds literal segments automatically, so never manually concatenate constant strings—the optimizer handles this in FlattenStringConcatenationLowering.

The Plus Operator for Mixed Concatenation

The + operator receives identical optimization to string templates. When you write a + b + c, the flattening lowering treats it the same as "$a$b$c".

fun buildPath(directory: String, filename: String): String {
    return directory + "/" + filename
}

This is useful when you need to concatenate existing variables without the syntactic overhead of template braces. However, avoid using + inside loops, as the compiler cannot always lift the concatenation out of iterative blocks, potentially creating multiple intermediate objects.

buildString for Iterative or Conditional Logic

For large or iterative concatenations—such as building CSV files, SQL queries, or HTML—use buildString. This DSL function creates a single StringBuilder instance and provides a receiver scope for appending.

fun toCsv(values: List<String>): String = buildString {
    for ((index, value) in values.withIndex()) {
        if (index > 0) append(',')
        append(value)
    }
}

The buildString implementation internally uses StringBuilder().apply { ... }, ensuring only one object is allocated regardless of loop iterations. This is significantly more efficient than using + or templates inside a loop, which would generate a new concatenation operation per iteration.

Explicit StringBuilder for Advanced Control

When you need to pass the builder reference between functions or inspect intermediate capacity, use an explicit StringBuilder:

fun generateHtmlTable(rows: List<List<String>>): String {
    val sb = StringBuilder()
    sb.append("<table>")
    for (row in rows) {
        sb.append("<tr>")
        for (cell in row) {
            sb.append("<td>").append(cell).append("</td>")
        }
        sb.append("</tr>")
    }
    sb.append("</table>")
    return sb.toString()
}

This pattern is functionally equivalent to buildString but offers greater flexibility when you need to manipulate the builder state directly.

Compiler Configuration Options

While the default behavior is optimal for most cases, the compiler provides flags to force specific concatenation strategies. In compiler/arguments/src/org/jetbrains/kotlin/arguments/description/JvmCompilerArguments.kt, the -Xstring-concat flag allows you to override the backend choice:


# Force StringBuilder-based concatenation regardless of JVM version

kotlinc -Xstring-concat=inline MyFile.kt

# Force invokedynamic with constants (requires JVM 9+)

kotlinc -Xstring-concat=indy-with-constants MyFile.kt

The default mode (indy-with-constants on JVM 9+, inline otherwise) provides the best performance without manual intervention.

Summary

  • String templates ("$var") are the most idiomatic and efficient choice for simple kotlin string concatenation, compiling to invokedynamic on modern JVMs.
  • The + operator receives identical compiler optimizations as templates but should be avoided inside loops.
  • buildString is the optimal pattern for iterative or conditional concatenation, using a single StringBuilder instance under the hood.
  • The compiler's FlattenStringConcatenationLowering (in FlattenStringConcatenationLowering.kt) and StringConcatGenerator (in StringConcatGenerator.kt) automatically flatten chains and select the best bytecode strategy for your JVM target.
  • Use explicit StringBuilder only when you need to reference the builder object across multiple scope boundaries.

Frequently Asked Questions

What is the most performant way to concatenate strings in Kotlin?

For simple variable interpolation, string templates and the + operator are equally performant because the compiler flattens both into a single invokedynamic instruction on JVM 9+ or an optimized StringBuilder chain on older versions. For iterative concatenation, buildString is most performant as it allocates a single builder regardless of iteration count.

Does Kotlin compile string concatenation differently than Java?

Yes. While Java traditionally compiles + operations to StringBuilder chains at the source level, Kotlin's compiler uses the FlattenStringConcatenationLowering IR transformation to collapse nested concatenations before bytecode generation. On JVM 9+, Kotlin prefers StringConcatFactory.makeConcatWithConstants via invokedynamic, whereas older Java compilers do not use this mechanism unless targeting newer JVMs.

When should I use buildString instead of string templates?

Use buildString when constructing strings through loops, multiple conditional branches, or when appending many fragments where the final structure is not known at compile time. String templates are optimized for static structures with interpolated variables, while buildString excels at dynamic, procedural string construction.

Can I force the compiler to use a specific string concatenation method?

Yes. The -Xstring-concat compiler flag defined in JvmCompilerArguments.kt accepts indy, indy-with-constants, or inline values to force invokedynamic or StringBuilder-based generation respectively. However, the default automatic selection based on JVM target is recommended for production code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →