# Primary and Secondary Constructors in a Kotlin Class: Delegation and Initialization Explained

> Master Kotlin constructors Learn primary and secondary constructor delegation and initialization for robust object creation Ensure consistent object setup in your Kotlin classes.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: deep-dive
- Published: 2026-02-13

---

**A Kotlin class can have exactly one primary constructor and zero or more secondary constructors, where every secondary constructor must explicitly delegate to the primary constructor or a superclass constructor to guarantee consistent object initialization.**

In the JetBrains/kotlin compiler source code, the relationship between **primary and secondary constructors in a Kotlin class** is modeled through distinct FIR (Frontend IR) and PSI (Program Structure Interface) structures that enforce strict initialization rules. The primary constructor defines the class header parameters and initialization logic, while secondary constructors provide alternative construction paths that must ultimately route through the primary constructor or a super constructor.

## The Primary Constructor in Kotlin

The **primary constructor** is declared directly in the class header after the class name. It can include property declarations and visibility modifiers, but it cannot contain executable code—instead, you use `init` blocks for initialization logic.

In the compiler's FIR layer, the primary constructor is represented by [[`FirPrimaryConstructor.kt`](https://github.com/JetBrains/kotlin/blob/main/FirPrimaryConstructor.kt)](https://github.com/JetBrains/kotlin/blob/master/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/impl/FirPrimaryConstructor.kt). This class holds the parameter list and maintains an optional delegated constructor reference (used primarily for enum classes), though its body is always `null` since initialization logic resides in `init` blocks.

The PSI layer exposes this through [[`KtClass.kt`](https://github.com/JetBrains/kotlin/blob/main/KtClass.kt)](https://github.com/JetBrains/kotlin/blob/master/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtClass.kt), which provides access to primary constructor parameters and the associated initialization blocks.

```kotlin
// Primary constructor declared in the class header
class User(val name: String, var age: Int) {
    init {
        require(age >= 0) { "Age must be non-negative" }
    }
}

```

## Secondary Constructors and Delegation Requirements

**Secondary constructors** are declared inside the class body using the `constructor` keyword. According to the Kotlin language specification implemented in the compiler, each secondary constructor must delegate to either the primary constructor using `this(...)` or a superclass constructor using `super(...)`.

The FIR implementation in [[`FirConstructorImpl.kt`](https://github.com/JetBrains/kotlin/blob/main/FirConstructorImpl.kt)](https://github.com/JetBrains/kotlin/blob/master/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/impl/FirConstructorImpl.kt) stores the secondary constructor's parameters, its body, and critically, a `delegatedConstructor` reference of type `FirDelegatedConstructorCall` that points to the target primary or super constructor.

At the PSI level, [[`KtSecondaryConstructor.kt`](https://github.com/JetBrains/kotlin/blob/main/KtSecondaryConstructor.kt)](https://github.com/JetBrains/kotlin/blob/master/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtSecondaryConstructor.kt) exposes the `delegatedConstructorCall` property that the frontend uses to validate delegation compliance during resolution.

```kotlin
class User {
    val name: String
    var age: Int

    // Explicit primary constructor inside the class body
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    // Secondary constructor delegating to the primary constructor
    constructor(name: String) : this(name, 0) {
        // Additional initialization specific to this constructor
    }
}

```

## How the Compiler Models Constructor Relationships

When the Kotlin compiler resolves a class declaration, it constructs a **single initialization chain**: secondary constructor → primary constructor → superclass constructor. This guarantees that all property initializers and `init` blocks execute exactly once regardless of which constructor entry point is used.

The resolution process works as follows:

1. The compiler first builds the `FirPrimaryConstructor` node from the class header parameters
2. For each secondary constructor, it creates a `FirConstructorImpl` node and wires its `delegatedConstructor` reference to the appropriate primary constructor call or superclass constructor call
3. The compiler verifies that no secondary constructor bypasses the primary constructor unless it directly calls a super constructor

This delegation requirement ensures that initialization logic defined in the primary constructor's `init` blocks always executes, maintaining object consistency across all construction paths.

## Practical Implementation Patterns

You can combine primary and secondary constructors to provide flexible APIs while maintaining centralized initialization logic.

**Pattern 1: Primary constructor with secondary convenience constructors**

```kotlin
// Primary constructor in header with secondary delegating to it
class Person(val firstName: String, val lastName: String, val age: Int) {
    constructor(firstName: String, lastName: String) : this(firstName, lastName, 0)
    constructor(fullName: String) : this(fullName.split(" ")[0], fullName.split(" ")[1])
}

```

**Pattern 2: Explicit primary constructor with secondary chaining**

```kotlin
class DatabaseConnection {
    val url: String
    val timeout: Int

    // Explicit primary constructor
    constructor(url: String, timeout: Int) {
        this.url = url
        this.timeout = timeout
        // Complex initialization logic here
    }

    // Secondary constructor delegating to primary
    constructor(url: String) : this(url, 30)

    // Another secondary constructor delegating to the previous secondary
    constructor() : this("localhost:5432")
}

```

## Summary

- A Kotlin class can have **only one primary constructor**, declared in the class header or explicitly inside the body, which handles property initialization and `init` blocks.
- Secondary constructors declared with the `constructor` keyword must delegate to the primary constructor via `this(...)` or to a superclass constructor via `super(...)`.
- The compiler enforces this relationship through `FirPrimaryConstructor` and `FirConstructorImpl` nodes, where the latter maintains a `delegatedConstructor` reference ensuring the initialization chain remains intact.
- This delegation model guarantees that all construction paths execute the primary constructor's initialization logic exactly once.

## Frequently Asked Questions

### Can a Kotlin class have multiple primary constructors?

No, a Kotlin class can have exactly one primary constructor or none at all. If you declare parameters in the class header, that becomes the primary constructor. If you omit the class header parameters, you can declare an explicit primary constructor inside the class body, but you cannot declare multiple primary constructors—the compiler treats additional constructors as secondary constructors that must follow delegation rules.

### What happens if a secondary constructor does not delegate to the primary constructor?

The Kotlin compiler will generate an error. According to the language specification implemented in the FIR layer, every secondary constructor must either call the primary constructor using `this(...)` or call a superclass constructor using `super(...)`. If you define a primary constructor in the class, secondary constructors cannot directly call super—they must route through the primary constructor to ensure consistent initialization of class properties.

### Can secondary constructors delegate to other secondary constructors?

Yes, secondary constructors can chain to other secondary constructors, provided the chain eventually reaches the primary constructor or a superclass constructor. The compiler validates this during resolution by following the `delegatedConstructor` references in the FIR nodes, ensuring no circular delegation exists and that all paths terminate at the primary constructor or a super constructor.

### Where does initialization logic run when using secondary constructors?

Initialization logic defined in `init` blocks and property initializers runs after the primary constructor executes but before the secondary constructor body executes. When you call a secondary constructor, it delegates to the primary constructor (either directly or through another secondary), which triggers all `init` blocks, and then control returns to the secondary constructor body to execute any additional setup code.