Question
Kotlin init Blocks vs Constructors: Differences and Usage
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
initblocks, 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.nameandageare constructor parameters that also become mutable properties because they usevar.professionis initialized when aPersonis created.- The
initblock then runs and printsTest.
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
initblock 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.
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:
- The primary constructor receives
"Maya"and28. nameis assigned"Maya"andageis assigned28.professionis initialized to"Developer".- The first
initblock runs and printsChecking Maya. - The second
initblock runs and printsCreated Maya, age 28, profession Developer. - The fully initialized
Personobject is assigned toperson.
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.
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
| Feature | Primary constructor | init block | Secondary constructor |
|---|---|---|---|
| Main purpose | Receive values required to create an object | Run setup or validation code | Offer another way to create an object |
| Location | Class header | Class body | Class body |
| Can accept arguments directly? | Yes | No; it uses constructor parameters/properties | Yes |
| Runs on object creation? | Yes | Yes | Yes, for that construction path |
| Can there be more than one? | No | Yes | Yes |
| Typical use | class User(val id: String) | Validate id or compute state | Create User from a shorter input |
val and var also affect constructor properties:
| Declaration | Meaning |
|---|---|
val name: String | Read-only property after construction |
var name: String | Mutable property after construction |
name: String | Constructor parameter only; not stored as a property automatically |
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.
valcreates a read-only property;varcreates a mutable property.- An
initblock runs automatically whenever an instance is created. - Use
initfor validation, normalization, and simple derived setup. - Property initializers and
initblocks execute in top-to-bottom order. - A class may have multiple
initblocks. - 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 throwsIllegalArgumentExceptionwhen the condition is false. - Keep
initblocks 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.
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.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.