Question

Kotlin init Blocks vs Constructors: Differences and Usage

kotlinconstructorinitialization-block

Question

I am learning Kotlin and would like to understand the difference between an init block and a constructor. When should each one be used, and how can they improve class initialization?

class Person constructor(var name: String, var age: Int) {
    var profession: String = "test"

    init {
        println("Test")
    }
}

Short Answer

Kotlin constructors receive the data needed to create an object. An init block runs initialization code whenever an object is created. You will learn how primary and secondary constructors work, when init executes, and how to use it for validation and derived setup.

Concept

A constructor defines how an object is created. It can accept values such as a person's name and age.

In Kotlin, a class can have:

  • A primary constructor, written in the class header.
  • One or more secondary constructors, written inside the class body with constructor(...).
  • One or more init blocks, which contain code that runs during object creation.

In this class:

class Person(var name: String, var age: Int) {
    var profession: String = "test"

    init {
        println("Test")
    }
}
  • Person(var name: String, var age: Int) is the primary constructor.
  • name and age are constructor parameters that also become mutable properties because they use var.
  • profession is initialized when a Person is created.
  • The init block then runs and prints Test.

An init block is not a separate constructor. It is setup code associated with object initialization. It is especially useful when you need to validate constructor input, calculate values, or perform setup that requires more than a single property assignment.

For example, an age should not be negative. An init block can enforce that rule immediately, preventing invalid Person objects from existing.

Mental Model

Think of creating an object as checking in to a hotel:

  • The constructor is the registration form. It collects required details, such as a guest name and number of nights.
  • Property initializers are the hotel preparing default items in the room.
  • The init block is the staff checking that the registration details are valid and completing setup before handing over the room key.

The object is only ready to use after its constructor values, property initializers, and init blocks have completed.

Take Quiz

Syntax and Examples

A primary constructor is usually written directly after the class name:

class Person(val name: String, val age: Int)

Use val for properties that should not change after creation, and var for properties that may change.

Add an init block when the class needs extra initialization work:

class Person(val name: String, val age: Int) {
    val isAdult: Boolean

    init {
        require(name.isNotBlank()) { "Name cannot be blank." }
        require(age >= 0) { "Age cannot be negative." }

        isAdult = age >= 18
    }
}

val person = Person("Amina", 22)
println(person.isAdult) // true

Here, the primary constructor receives name and age. The init block validates both values and calculates the isAdult property.

You can also assign a property directly from a constructor parameter when no extra logic is needed:

class Book(val title: String, val pages: Int)

A secondary constructor is useful when you want another way to create the same class:

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

val unknownAgePerson = Person("Noah")

Every secondary constructor must delegate to the primary constructor, directly or indirectly, using this(...).

Step by Step Execution

Consider this class:

class Person(val name: String, val age: Int) {
    val profession = "Developer"

    init {
        println("Checking $name")
    }

    init {
        println("Created $name, age $age, profession $profession")
    }
}

val person = Person("Maya", 28)

When Kotlin executes Person("Maya", 28), it performs initialization in this order:

  1. The primary constructor receives "Maya" and 28.
  2. name is assigned "Maya" and age is assigned 28.
  3. profession is initialized to "Developer".
  4. The first init block runs and prints Checking Maya.
  5. The second init block runs and prints Created Maya, age 28, profession Developer.
  6. The fully initialized Person object is assigned to person.

For properties and init blocks written in the class body, Kotlin follows their top-to-bottom order. This matters when one initializer depends on another property.

Real World Use Cases

init blocks are commonly used for work that must happen as soon as an object is created:

  • Input validation: Ensure an API request has a non-empty email or a positive quantity.
  • Derived values: Calculate a cart subtotal from item prices.
  • Normalization: Trim whitespace from a username or convert a code to uppercase.
  • Configuration checks: Ensure required settings are present before a service runs.
  • Domain rules: Prevent creation of an order with no items or a bank transfer with a non-positive amount.

Example: validating a product model used by an application:

class Product(val name: String, val price: Double) {
    init {
        require(name.isNotBlank()) { "Product name is required." }
        require(price >= 0) { "Price cannot be negative." }
    }
}

The constructor states what is needed to create a product. The init block protects the application's rule that products cannot have invalid names or prices.

Take Quiz

Real Codebase Usage

In real Kotlin projects, keep constructors focused on the data needed to create an object. Use init for short, essential initialization logic.

A common validation pattern is require:

class UserId(value: String) {
    val value = value.trim()

    init {
        require(this.value.isNotEmpty()) { "User ID cannot be empty." }
    }
}

This class normalizes the input and validates the normalized value. The this.value expression refers to the property, while value by itself refers to the constructor parameter.

For alternate creation paths, use secondary constructors sparingly. Factory functions are often clearer when creation has named intent:

class Account private constructor(val email: String, val verified: Boolean) {
    companion object {
        fun unverified(email: String): Account {
            return Account(email.trim(), false)
        }
    }
}

Avoid putting slow work, network calls, database queries, or complex business workflows in init. Object construction should remain predictable, fast, and easy to test.

Common Mistakes

Treating init as a constructor

init runs during construction, but it cannot be called directly like a constructor.

class Person(val name: String) {
    init {
        println(name)
    }
}

// Person.init() // Invalid: init is not a callable function

Create the object with Person("Maya"); Kotlin runs init automatically.

Forgetting that constructor parameters are not always properties

Without val or var, a primary-constructor parameter is only available during initialization.

class Person(name: String) {
    init {
        println(name)
    }

    // fun greet() = println(name) // Invalid: name is not a property here
}

Use val name: String or var name: String if other methods need it.

Using var when a value should not change

class Person(var name: String, var age: Int)

This allows callers to change both properties at any time. Prefer val unless mutation is genuinely required:

class Person(val name: String, val age: Int)

Validating after the object has already been used

Validate constructor input in init so invalid objects cannot be created:

class Ticket(val quantity: Int) {
    init {
        require(quantity > 0) { "Quantity must be positive." }
    }
}

Performing expensive work in init

Avoid downloads, database calls, and long calculations in an init block. They make object creation unexpectedly slow and can make error handling harder. Use a normal method, coroutine-based service, or factory function instead.

Reading properties before they are initialized

Because initialization follows source order, do not use a later property from an earlier initializer:

class Example {
    val first = second + 1 // Problem: second is not initialized yet
    val second = 10
}

Declare second first or calculate first inside a later init block.

Comparisons

FeaturePrimary constructorinit blockSecondary constructor
Main purposeReceive values required to create an objectRun setup or validation codeOffer another way to create an object
LocationClass headerClass bodyClass body
Can accept arguments directly?YesNo; it uses constructor parameters/propertiesYes
Runs on object creation?YesYesYes, for that construction path
Can there be more than one?NoYesYes
Typical useclass User(val id: String)Validate id or compute stateCreate User from a shorter input

val and var also affect constructor properties:

DeclarationMeaning
val name: StringRead-only property after construction
var name: StringMutable property after construction
name: StringConstructor parameter only; not stored as a property automatically
Take Quiz

Cheat Sheet

class Person(val name: String, val age: Int) {
    init {
        require(name.isNotBlank())
        require(age >= 0)
    }
}
  • The primary constructor appears after the class name.
  • val creates a read-only property; var creates a mutable property.
  • An init block runs automatically whenever an instance is created.
  • Use init for validation, normalization, and simple derived setup.
  • Property initializers and init blocks execute in top-to-bottom order.
  • A class may have multiple init blocks.
  • A class has at most one primary constructor but may have multiple secondary constructors.
  • A secondary constructor delegates using this(...) when a primary constructor exists.
  • Prefer require(condition) for invalid caller arguments; it throws IllegalArgumentException when the condition is false.
  • Keep init blocks quick and free of external side effects when possible.

FAQ

Is init a constructor in Kotlin?

No. An init block is initialization code that Kotlin runs as part of object construction. The primary or secondary constructor is the actual constructor.

Can a Kotlin class have multiple init blocks?

Yes. Kotlin runs them in the order they appear in the class body, together with property initializers.

When should I use an init block in Kotlin?

Use it when construction requires validation, input cleanup, or calculation that does not fit in a simple property assignment.

Can an init block receive parameters?

No. It has no parameter list. It can access primary constructor parameters and class properties that are in scope.

Should I use val or var in a constructor?

Use val by default. Use var only when the property must change after the object is created.

Does init run for a secondary constructor?

Yes. A secondary constructor delegates through the class initialization process, so property initializers and init blocks run when the object is created.

Can I return a value from an init block?

No. An init block initializes the current object and does not return a separate value.

Can I call a function from an init block?

Yes, but be careful. Prefer functions that do not depend on subclass state or uninitialized properties, and avoid expensive external operations.

Take Quiz

Mini Project

Description

Create a small TemperatureReading class for a weather or sensor application. The constructor will receive a city and Celsius temperature, while an init block will validate and normalize the incoming data.

Goal

Build a safe temperature model that rejects invalid city names and exposes a calculated Fahrenheit value.

Requirements

Define a TemperatureReading class with city and celsius constructor properties.

Trim whitespace from the city name before storing it.

Reject blank city names with require.

Reject Celsius values below absolute zero (-273.15).

Add a read-only fahrenheit property calculated from Celsius.

Create and print one valid reading.

Take Quiz

Keep learning

Related questions

View more