String Templates

String templates are Kotlin’s built-in way to embed variables and expressions directly inside a string literal, instead of gluing pieces together with the + operator. Anywhere you would write "Hello, " + name + "!" in Java, Kotlin lets you write "Hello, $name!" and the compiler evaluates the expression and inserts its string form for you. Templates work inside both ordinary double-quoted strings and Kotlin’s triple-quoted raw strings, and they can hold anything from a single variable name to a full method call. They are one of the first features that make Kotlin feel noticeably more pleasant to write than Java.

Overview / How it works

A string template is any string literal that contains a dollar sign ($) followed by either a simple name or a curly-braced expression. When the compiler sees $identifier, it looks up that identifier in the current scope, calls toString() on its value, and splices the result into the string at that position. When it sees ${expression}, it evaluates the full expression inside the braces — a method call, arithmetic, an index access, even another string template — and again inserts the string form of the result.

The $identifier shorthand only works for a single, bare name immediately after the $ — it stops as soon as it hits a character that cannot be part of an identifier, such as a dot, a space, or punctuation. That means "$user.name" does not access the name property of user; it interpolates user alone (calling its toString()) and then appends the literal text .name. To reach a property, call a function, use an operator, or index into a collection, you must use the ${expression} form with braces. This is one of the most common sources of confusion for newcomers, and it is covered in more detail in Common Mistakes below.

Under the hood, string templates are resolved entirely at compile time into ordinary string-building code — there is no reflection or runtime parsing involved. Depending on the target JVM version, the Kotlin compiler either builds the string with a chain of StringBuilder.append calls or, on newer JVM targets, emits an invokedynamic call to StringConcatFactory (the same mechanism the Java compiler uses for Java’s own + concatenation). Either way, by the time your code runs, the template has already been turned into plain, efficient string-building bytecode; nothing about it is slower than manual concatenation, and it is almost always easier to read.

Templates also respect Kotlin’s null safety rules. If $identifier or ${expression} evaluates to a value of a nullable type, Kotlin is happy to call toString() on it — a null value simply renders as the four-character string "null". What the compiler will not let you do is dereference a member (a property or function) of a nullable value without a null check first, exactly as it would refuse that dereference anywhere else in your code. Inside ${...} you still need ?., ?:, or a prior null check — the braces do not grant an exemption from null safety.

Syntax

There are two forms of interpolation:

Form When to use it Example
$name Inserting a single variable or property reference with no further access "Hi, $name"
${expression} Inserting the result of an expression: property access, method calls, operators, indexing "Total: ${price * qty}"

To include a literal dollar sign that should not start a template, escape it. In a regular double-quoted string, use the backslash escape \$. In a triple-quoted raw string, backslash escapes do not work at all (raw strings ignore all backslash escapes), so you must use ${'$'} instead — a template expression whose value is the single character '$'.

val price = 5
println("Price: \$$price")       // regular string escape
println("""Price: ${'$'}$price""") // raw string escape

Examples

Example 1: Basic interpolation

The simplest use of a template is dropping a variable straight into a string.

fun main() {
    val name = "Kotlin"
    val version = 2

    println("Hello, $name!")
    println("You are learning Kotlin version $version.")
}

Output:

Hello, Kotlin!
You are learning Kotlin version 2.

Both $name and $version are simple identifiers, so Kotlin resolves them directly: it calls toString() on each value (a no-op for String, and the standard numeric formatting for Int) and inserts the result in place.

Example 2: Expressions with ${…}

Once you need more than a bare variable — arithmetic, a property, a function call — switch to the curly-brace form.

fun main() {
    val a = 8
    val b = 5

    println("The sum of $a and $b is ${a + b}.")

    val items = listOf("pen", "notebook", "eraser")
    println("You have ${items.size} items: ${items.joinToString(", ")}.")
}

Output:

The sum of 8 and 5 is 13.
You have 3 items: pen, notebook, eraser.

${a + b} evaluates the addition first and interpolates the resulting Int. ${items.joinToString(", ")} calls a full method with an argument — something the $identifier shorthand could never do, since it only accepts a bare name.

Example 3: Data classes, properties, and null-safe defaults

Templates combine naturally with data classes and the Elvis operator to print friendly, null-safe messages.

data class User(val username: String, val email: String?)

fun main() {
    val user = User("codewiz", null)

    println("Username: ${user.username}")
    println("Email: ${user.email ?: "not provided"}")

    val greeting = "Welcome back, ${user.username}!"
    println(greeting)
}

Output:

Username: codewiz
Email: not provided
Welcome back, codewiz!

user.email is a nullable String?, so the template uses the Elvis operator ?: to substitute a fallback when it is null. Notice that reaching the username property required ${user.username} with braces — $user.username would only interpolate user‘s toString() (its auto-generated data class representation) followed by the literal text .username.

Example 4: Raw strings and escaping a literal dollar sign

Triple-quoted raw strings still support templates, but they need the ${'$'} trick to print a literal $ because raw strings ignore backslash escapes entirely.

fun main() {
    val price = 19.99
    val product = "Kotlin Mug"

    println("The $product costs \$$price.")

    val receipt = """
        Item: $product
        Price: ${'$'}$price
    """.trimIndent()
    println(receipt)
}

Output:

The Kotlin Mug costs $19.99.
Item: Kotlin Mug
Price: $19.99

In the regular string, \$ escapes the dollar sign so it is treated as a literal character, and the following $price still interpolates normally. In the raw (triple-quoted) string, backslash has no special meaning at all, so ${'$'} — a template whose expression is the character literal '$' — is the only way to get a literal dollar sign next to an interpolated value. trimIndent() also strips the common leading whitespace from each line, which is the idiomatic way to keep multi-line raw strings readable in source without embedding that indentation in the output.

How it works step by step

When the compiler processes a string literal containing templates, it walks the literal from left to right and splits it into a sequence of pieces: literal text segments and template segments. For each template segment it does the following:

  • If it is the $identifier form, it resolves identifier as a value in the enclosing scope (a local variable, parameter, property, and so on) at compile time.
  • If it is the ${expression} form, it type-checks the full expression exactly as if it appeared anywhere else in your code — including enforcing null safety on any member access inside it.
  • It inserts an implicit call to that value’s toString() (using the special-cased literal "null" if the value is null).
  • It stitches the literal segments and the stringified results together, at compile time, into efficient string-building bytecode (a StringBuilder chain or an invokedynamic concatenation, depending on the JVM target).

None of this happens at runtime through parsing text — by the time your program executes, the template has already been compiled down to the same kind of code you would have written by hand with a StringBuilder. This is why template performance is essentially identical to manual concatenation, and why a typo inside ${...} (an unresolved reference, a type mismatch, a missed null check) is a compile error, not a runtime surprise.

Common Mistakes

Mistake 1: Forgetting braces for property or method access

The $identifier shorthand stops at the first non-identifier character, so following it with a dot does not reach into the object — it just appends literal text.

data class Point(val x: Int, val y: Int)

fun main() {
    val p = Point(3, 4)
    println("Point x is $p.x")
}

Output:

Point x is Point(x=3, y=4).x

This compiles fine, which is exactly what makes it dangerous — there is no error to catch the mistake, just wrong output. $p interpolates p‘s auto-generated toString(), and .x is left over as plain text. Wrap the whole expression in braces to actually access the property:

data class Point(val x: Int, val y: Int)

fun main() {
    val p = Point(3, 4)
    println("Point x is ${p.x}")
}

Output:

Point x is 3

Mistake 2: Accessing a nullable member without a null check

Templates do not bypass null safety. Calling a member on a nullable value inside ${...} still needs a safe call or a prior check, or it will not compile.

fun main() {
    val name: String? = null
    println("Length: ${name.length}")
}

Output:

Compiler error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

Use ?. together with the Elvis operator ?: to supply a fallback instead of failing to compile:

fun main() {
    val name: String? = null
    println("Length: ${name?.length ?: "unknown"}")
}

Output:

Length: unknown

Mistake 3: Trying to escape $ with a backslash inside a raw string

Backslash escapes (\n, \t, \$, and friends) only work in regular double-quoted strings. Triple-quoted raw strings treat backslash as an ordinary character, so \$ does not stop interpolation.

fun main() {
    val amount = 42
    val note = """
        Total: \$amount
    """.trimIndent()
    println(note)
}

Output:

Total: \42

The backslash is printed as a literal character, and $amount still interpolates right after it — not the intended literal dollar sign at all. Use ${'$'} in raw strings instead:

fun main() {
    val amount = 42
    val note = """
        Total: ${'$'}$amount
    """.trimIndent()
    println(note)
}

Output:

Total: $42

Best Practices

  • Prefer string templates over + concatenation; they read left-to-right like the final text and avoid building unnecessary intermediate strings.
  • Use the bare $name form only for a single identifier; reach for ${expression} as soon as you need a property, a method call, an operator, or indexing.
  • Add braces even when they are technically optional if it makes the boundary of the interpolated part clearer to a reader, such as when a template sits right next to other text that could be misread.
  • Handle nullable values explicitly inside templates with ?: rather than letting them silently print the string "null", unless that is genuinely what you want the user to see.
  • Use triple-quoted raw strings with .trimIndent() for multi-line templated text (SQL, JSON fragments, help text) instead of concatenating multiple lines with \n.
  • Remember ${'$'} is the only reliable way to emit a literal dollar sign inside a raw string; do not reach for a backslash there.
  • Avoid calling expensive or side-effecting functions inside a template purely for convenience — the call still executes every time the string is built, even if the resulting text is never shown.

Practice Exercises

  • Declare val name: String and val age: Int with your own values, then use a single string template to print a sentence in the form My name is <name> and I am <age> years old. with the real values substituted in.
  • Given val numbers = listOf(4, 8, 15, 16, 23, 42), print a sentence that reports both numbers.size and numbers.sum() using ${expression} templates inside a single println call.
  • Declare val nickname: String? = null, then write a template that prints a greeting using the nickname if it is present, or falls back to the word friend when it is null — without using !!. Expected output when nickname is null: Hello, friend!

Summary

  • A string template inserts a value into a string literal using $name for a bare identifier or ${expression} for anything more complex.
  • $identifier stops at the first non-identifier character, so property access, method calls, and operators all require the ${...} form.
  • Templates are resolved entirely at compile time into efficient string-building bytecode — there is no runtime parsing overhead.
  • Null safety still applies inside ${...}: a nullable value renders as "null" on its own, but accessing a member of it still needs ?. or a null check.
  • Escape a literal $ with \$ in regular strings, and with ${'$'} in triple-quoted raw strings, since backslash escapes do not work there.
  • Prefer templates over manual + concatenation for clarity, and pair them with .trimIndent() for readable multi-line text.