Question

Kotlin Secondary Constructors: Syntax, Delegation, and Examples

kotlinsyntaxconstructor

Question

How do you declare a secondary constructor in Kotlin, and where is the official documentation? Why does this class fail to compile?

class C(a: Int) {
    // Intended secondary constructor
    this(s: String) : this(s.length) {
        // ...
    }
}

Short Answer

You will learn the syntax for Kotlin secondary constructors, how they delegate to a primary constructor with this(...), and when a constructor body is useful. You will also see why a secondary constructor must begin with the constructor keyword.

Concept

A constructor creates and initializes an object.

Kotlin supports two kinds of constructors:

  • A primary constructor, written in the class header.
  • A secondary constructor, written inside the class body with the constructor keyword.

A class can have zero or one primary constructor and can have multiple secondary constructors. When a class has a primary constructor, every secondary constructor must delegate to it, directly or indirectly.

class C(val a: Int) {
    constructor(s: String) : this(s.length)
}

Here, constructor(s: String) is the secondary constructor. The expression : this(s.length) calls the primary constructor, passing the length of s as its a argument.

This delegation rule matters because it guarantees that the primary initialization path runs before the secondary constructor body. It prevents an object from being only partially initialized.

Official Kotlin documentation covers this under Classes and Constructors, especially the section on secondary constructors.

Mental Model

Think of the primary constructor as the main entrance to a building.

A secondary constructor is another entrance, such as a side door. Before someone can enter through the side door, they must be directed to the main entrance so the standard check-in process happens.

constructor(s: String) : this(s.length)

The side door accepts a String, but this(s.length) redirects to the main entrance, which accepts an Int. After the main setup is complete, the secondary constructor can perform its own extra work.

Take Quiz

Syntax and Examples

Use constructor inside the class body, followed by its parameters. Put : this(...) after the parameter list to delegate to another constructor in the same class.

class C(val a: Int) {
    constructor(s: String) : this(s.length) {
        println("Created C from the text: $s")
    }
}

fun main() {
    val fromNumber = C(5)
    val fromText = C("hello")

    println(fromNumber.a) // 5
    println(fromText.a)   // 5
}

C(5) calls the primary constructor directly.

C("hello") calls the secondary constructor. It calculates "hello".length, delegates to the primary constructor as this(5), then runs the secondary constructor body.

A secondary constructor can also delegate to another secondary constructor, but the chain must eventually reach the primary constructor when one exists:

class User(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)

    constructor() : this("Anonymous")
}

Step by Step Execution

Consider this code:

class Message(val text: String, val priority: Int) {
    constructor(text: String) : this(text, 1) {
        println("Using the default priority")
    }
}

fun main() {
    val message = Message("Server started")
    println(message.priority)
}

When Message("Server started") runs:

  1. Kotlin finds a constructor that accepts one String.
  2. The secondary constructor delegates with this(text, 1).
  3. The primary constructor initializes text to "Server started" and priority to 1.
  4. The secondary constructor body runs and prints Using the default priority.
  5. The completed Message object is assigned to message.
  6. println(message.priority) prints 1.

The output is:

Using the default priority
1

Real World Use Cases

Secondary constructors are useful when a class should accept several input formats but should initialize itself through one main path.

  • Default values from a shorter input: Create a User with only a name and choose a default role.
  • Parsing input: Accept a date string, parse it, then delegate using numeric date values.
  • Convenience creation: Accept a configuration object in one constructor and individual values in another.
  • Java interoperability: Java callers cannot always use Kotlin default arguments as conveniently, so constructors can provide Java-friendly overloads.
  • Legacy APIs: Provide an alternate initialization form while keeping a consistent internal representation.

Example: accepting an RGB hex value while storing separate color channels.

class Color(val red: Int, val green: Int, val blue: Int) {
    constructor(hex: String) : this(
        hex.substring(1, 3).toInt(16),
        hex.substring(3, 5).toInt(16),
        hex.substring(5, 7).toInt(16)
    )
}
Take Quiz

Real Codebase Usage

In modern Kotlin codebases, developers often prefer primary constructor parameters with default values over many secondary constructors.

class Connection(
    val host: String,
    val port: Int = 443,
    val useSsl: Boolean = true
)

This is concise for Kotlin callers:

val local = Connection("localhost", port = 8080, useSsl = false)
val secure = Connection("api.example.com")

Use a secondary constructor when it adds a genuinely different input form or conversion:

class Product(val id: Int, val name: String) {
    constructor(csvLine: String) : this(
        csvLine.substringBefore(",").toInt(),
        csvLine.substringAfter(",")
    )
}

For more complex parsing, validation, I/O, or error handling, a named factory function is often clearer because its name can describe what it does:

class Product private constructor(val id: Int, val name: String) {
    companion object {
        fun fromCsv(csvLine: String): Product {
            val parts = csvLine.split(",", limit = 2)
            require(parts.size == 2) { "Expected id,name" }
            return Product(parts[0].toInt(), parts[1])
        }
    }
}

Factory names such as fromCsv, fromJson, or fromEnvironment make conversion work explicit.

Common Mistakes

Omitting the constructor keyword

This does not compile because this(...) cannot declare a constructor by itself:

class C(a: Int) {
    this(s: String) : this(s.length)
}

Write constructor:

class C(val a: Int) {
    constructor(s: String) : this(s.length)
}

Forgetting to delegate to the primary constructor

If a primary constructor exists, a secondary constructor must delegate to it.

class C(val a: Int) {
    // Does not compile
    constructor(s: String) {
        println(s)
    }
}

Correct version:

class C(val a: Int) {
    constructor(s: String) : this(s.length) {
        println(s)
    }
}

Doing work before this(...)

Constructor delegation is part of the constructor declaration, not a statement that can be placed later in the body.

class C(val a: Int) {
    // Invalid
    constructor(s: String) {
        println(s)
        this(s.length)
    }
}

Put delegation after the parameter list:

constructor(s: String) : this(s.length) {
    println(s)
}

Expecting a to be a property

A primary constructor parameter is not automatically accessible as a property after construction. Add val or var when the object needs to store it.

class C(a: Int) // `a` is only available during initialization

class StoredC(val a: Int) // `a` is a readable property

Creating confusing overloads

Avoid constructors whose arguments are easy to mix up, especially multiple parameters of the same type. Prefer named factory functions or a configuration data class when clarity is more important than a short call.

Comparisons

ApproachBest useExample
Primary constructorThe normal, required initialization dataclass User(val name: String)
Secondary constructorA convenient alternate input formconstructor(email: String) : this(email.substringBefore("@"))
Default parameterOptional values with a sensible defaultclass User(val name: String, val active: Boolean = true)
Named factory functionParsing, validation, or creation that needs a descriptive namefun fromCsv(line: String): User
init blockValidation or setup shared by all construction pathsinit { require(name.isNotBlank()) }

A secondary constructor is not a replacement for an init block. init blocks run whenever the primary constructor is reached, including when construction starts from a secondary constructor.

class Account(val id: String) {
    init {
        require(id.isNotBlank())
    }

    constructor(idNumber: Int) : this(idNumber.toString())
}
Take Quiz

Cheat Sheet

  • Declare a secondary constructor inside the class body with constructor.
  • Syntax:
constructor(parameters) : this(arguments) {
    // optional extra work
}
  • this(...) delegates to another constructor in the same class.
  • If the class has a primary constructor, each secondary constructor must eventually call it.
  • The delegated constructor completes its initialization before the secondary constructor body runs.
  • Add val or var to primary constructor parameters that should become properties.
  • Prefer default parameters for simple optional values.
  • Prefer a named factory function for parsing or complex creation logic.
class C(val value: Int) {
    constructor(text: String) : this(text.length)
}

FAQ

What is a secondary constructor in Kotlin?

It is an additional constructor declared inside a class body using the constructor keyword. It provides another way to create instances of the class.

How do I call the primary constructor from a Kotlin secondary constructor?

Use : this(arguments) after the secondary constructor parameter list.

constructor(text: String) : this(text.length)

Why does this(s: String) not compile in Kotlin?

this is used for constructor delegation, not constructor declaration. A secondary constructor must start with constructor(s: String).

Must every Kotlin secondary constructor call the primary constructor?

Yes, when a primary constructor exists. It can call it directly or delegate through another secondary constructor that eventually calls it.

Can a Kotlin class have multiple secondary constructors?

Yes. Each constructor must have a distinct parameter signature.

Should I use secondary constructors or default arguments?

Use default arguments for simple optional values. Use a secondary constructor when callers provide meaningfully different input, such as a string that must be converted into primary constructor values.

Can secondary constructors validate data?

Yes, but shared validation usually belongs in an init block or primary-constructor validation, so it applies to every construction path.

Where is the Kotlin documentation for secondary constructors?

See the official Kotlin documentation section named Classes and Constructors, under Secondary constructors.

Take Quiz

Mini Project

Description

Build a Temperature class that stores a temperature in Celsius while allowing callers to create it from either Celsius or Fahrenheit. This demonstrates an alternate input format delegated to one primary initialization path.

Goal

Create Temperature objects from Celsius values and Fahrenheit values, then display the normalized Celsius value.

Requirements

  • Create a class with a primary constructor that stores a Celsius Double value.
  • Add a secondary constructor that accepts a Fahrenheit Double value.
  • Delegate from the Fahrenheit constructor to the primary constructor.
  • Convert Fahrenheit to Celsius using (fahrenheit - 32) * 5 / 9.
  • Print the Celsius values for one object created each way.
Take Quiz

Keep learning

Related questions

View more