Start Learning
Javaneer
Back to stage
Stage 1·Syntax for Java Eyes

Strings & Basic Types

String templates instead of concatenation, triple-quoted raw strings, and a type system with no primitives - Int, Long, Double are real types, unified but compiled efficiently.

12 min readBeginner
On this page

Two everyday things read very differently in Kotlin: strings and basic types. String templates end the "..." + variable + "..." concatenation ritual, and triple-quoted strings handle multi-line text cleanly. And Kotlin's type system has no primitives - Int, Long, Double, Boolean are all real classes, unifying the type hierarchy while the compiler still uses efficient JVM primitives under the hood. Both are small daily wins for a Java developer.

String templates

In Java you build strings with + concatenation or String.format. Kotlin has string templates - embed a variable or expression directly with $:

val name = "Ada"
val age = 36
println("$name is $age years old")               // simple variable: $name
println("Next year she'll be ${age + 1}")        // an expression: ${...}
println("Name length: ${name.length}")           // any expression in ${}

$variable inserts a simple variable; ${expression} inserts any expression. Compare the Java: System.out.println(name + " is " + age + " years old") - the template version reads like the sentence it produces, with no + noise. This is the same idea as Java's newer text blocks and formatted, but inline and used constantly.

Multi-line and raw strings

Triple quotes create a raw string that spans lines and doesn't process escape sequences - ideal for JSON, SQL, or regex where Java's \\ escaping is painful:

val json = """
    {
        "name": "$name",
        "age": $age
    }
""".trimIndent()                                  // trimIndent() removes the common leading indentation

val regex = """\d{3}-\d{4}""".toRegex()           // no double-backslash escaping

trimIndent() is the idiomatic companion - it strips the leading whitespace you added for code readability, so the string's actual content starts at the left margin. (Templates still work inside triple-quoted strings, as $name shows.)

No primitives: a unified type system

Java splits types into primitives (int, double, boolean - lowercase, not objects) and their boxed wrappers (Integer, Double, Boolean - objects). This split causes autoboxing surprises and means primitives can't be used as generic type arguments (List<int> is illegal; you need List<Integer>).

Kotlin has only the class types - Int, Long, Short, Byte, Double, Float, Boolean, Char:

val count: Int = 42          // a real type, capital I - no separate 'int'
val big: Long = 10_000_000_000
val ratio: Double = 3.14
val flag: Boolean = true
val list: List<Int> = listOf(1, 2, 3)   // Int works directly as a generic argument

There's no int-vs-Integer distinction to trip over. But here's the clever part: the compiler still uses JVM primitives under the hood wherever possible. An Int local variable compiles to a JVM int (fast, no boxing); it only boxes to an Integer when it must (e.g. as a nullable Int? or a generic type argument), exactly when Java would. So you get a clean, unified type model in the source and efficient primitive performance in the bytecode - the best of both.

Numeric literals and conversions

A couple of details Java developers should note:

  • Underscores in literals for readability: 1_000_000, 0xFF_EC_DE.
  • No implicit widening. Kotlin won't silently convert an Int to a Long; you convert explicitly with .toLong(), .toDouble(), etc. This is stricter than Java (which auto-widens) and prevents a class of subtle bugs, at the cost of an occasional explicit .toX().

Int? boxes - nullability has a cost

Because a non-null Int compiles to a primitive int but a nullable Int? must be able to hold null, an Int? is boxed to an Integer under the hood. Usually irrelevant, but in hot loops or huge collections of nullable numbers it matters - a List<Int?> boxes every element. Prefer non-nullable numeric types on performance-sensitive paths, and know that nullability, not the Int/Integer syntax, is what triggers boxing in Kotlin.

One set of measuring cups, sized right behind the scenes

Java's primitives and wrappers are like having two entirely separate sets of measuring cups - a lightweight everyday set (int, double) and a fancy boxed set for special occasions (Integer, Double) - and constantly having to remember which drawer a recipe needs, sometimes swapping between them mid-cook (autoboxing). Kotlin gives you one labeled set of cups (Int, Double) that you use everywhere, so there's no drawer-juggling in your recipe (source code). Behind the kitchen door, the staff quietly hand you the lightweight cup when that's all that's needed and only fetch the heavier boxed one when the dish truly requires it (nullable or generic) - you get the convenience of one set with the efficiency of the right cup each time, without thinking about it. String templates are just writing the recipe in plain sentences ('add $sugar cups of sugar') instead of gluing fragments together.

Kotlin-ify the Java

Rewrite this Java in idiomatic Kotlin: it builds a greeting with concatenation - `String msg = "Hi " + user.getName()

  • ", you have " + count + " messages";- and separately declaresint count = 5;used in aList` of counts. Show the string template version and explain how Kotlin represents the int in the list versus as a plain local.

How does Kotlin's handling of basic types differ from Java's, in source and in bytecode?

Key takeaways

  • String templates embed variables and expressions directly: "$name is ${age + 1}" - no + concatenation, reading like the sentence produced.
  • Triple-quoted raw strings span multiple lines without escape processing (great for JSON/SQL/regex); trimIndent() strips the code-indentation.
  • Kotlin has no primitives in source - Int, Long, Double, Boolean are class types usable directly as generic arguments (no int-vs-Integer split).
  • The compiler still uses efficient JVM primitives under the hood, boxing only when necessary (a nullable Int? or a generic type argument) - same performance model as Java.
  • Numeric literals allow underscores (1_000_000), and Kotlin requires explicit conversions (.toLong(), .toDouble()) with no implicit widening - stricter than Java.
Was this lesson helpful?
Edit this page on GitHub