Classes, constructors and properties
A class models a thing in your program — a customer, an order, a book — bundling the data that describes it with the behaviour that acts on it. Kotlin's class syntax is dramatically shorter than Java's, and this lesson covers the shape of a class, its constructor, and its properties.
The shortest useful class
class Customer(val name: String, val email: String)
That one line is a complete, usable class. It declares a Customer with two read-only properties,
name and email, and a constructor that takes them. Compare with the equivalent Java — a class
declaration, two private fields, a constructor that assigns them, and two getters, easily fifteen
lines. Kotlin's class Customer(val name: String, val email: String) says the same thing with the
noise removed.
Create an instance with no new keyword — you just call the class like a function:
val customer = Customer("Kavita", "kavita@example.com")
println(customer.name) // Kavita
println(customer.email) // kavita@example.com
The primary constructor and properties in one
That header — (val name: String, val email: String) — is the primary constructor, and the
val keyword on each parameter is doing something powerful: it declares a property and the
constructor parameter that initialises it, together. Break it down:
val name: Stringin the constructor: a read-only propertyname, set from the argument.var age: Int: a mutable property — the object's age can change after creation.- A plain parameter with no
val/var(class C(name: String)): a constructor argument that is not kept as a property — usable only during construction.
So the choice of val, var, or nothing on a constructor parameter decides whether it becomes a
read-only property, a mutable one, or just a construction-time value. As everywhere, prefer val
— an object whose properties do not change is easier to reason about.
Properties with defaults, and named arguments
Constructor parameters can have defaults, and you construct with named arguments — the functions module, applied to objects:
class Customer(
val name: String,
val email: String,
val city: String = "Pune",
val isPremium: Boolean = false
)
val a = Customer("Kavita", "kavita@example.com")
val b = Customer(
name = "Ravi",
email = "ravi@example.com",
isPremium = true // skip city (defaulted), set only what differs
)
This is why the named-arguments lesson mattered: constructing an object with several fields reads like filling in a form, each field labelled, impossible to get the order wrong. This is the everyday way you will build objects in Kotlin.
init blocks — running code at construction
When you need to do something when an object is created — validate, compute, log — use an init
block:
class Customer(val name: String, val email: String) {
init {
require(name.isNotBlank()) { "name must not be blank" }
require("@" in email) { "email must contain @" }
}
}
val ok = Customer("Kavita", "kavita@example.com") // fine
val bad = Customer("", "kavita@example.com") // throws: name must not be blank
init runs as part of construction, after the properties are set. require(condition) { message }
throws an IllegalArgumentException with your message if the condition is false — the idiomatic way
to validate constructor arguments so an invalid object can never exist. An object that validates
itself at birth is one you never have to check again.
Computed properties and behaviour
Properties can be computed rather than stored, using a custom getter, and classes hold methods (functions that act on the object):
class Order(val itemPaise: Int, val qty: Int) {
// a computed property — no stored value, recalculated on each access
val totalPaise: Int
get() = itemPaise * qty
// a method
fun formatTotal(): String = "₹${totalPaise / 100}.${totalPaise % 100}"
}
val order = Order(itemPaise = 15000, qty = 3)
println(order.totalPaise) // 45000
println(order.formatTotal()) // ₹450.0
totalPaise has a get() and no stored value — every time you read it, it recomputes from
itemPaise and qty. This is the right way to expose something derived from other properties: it
can never fall out of sync, because there is nothing to keep in sync (the same idea as a generated
column in the database course). formatTotal() is a method — behaviour that belongs to the order.
Secondary constructors, briefly
Occasionally you want an alternative way to construct an object; a secondary constructor provides it:
class Customer(val name: String, val email: String) {
constructor(name: String) : this(name, "$name@example.com") // delegates to the primary
}
val c = Customer("kavita") // email defaults to kavita@example.com
In practice you rarely need these — default arguments on the primary constructor usually do the job more cleanly, and idiomatic Kotlin prefers them. Reach for a secondary constructor only when you need genuinely different construction logic that defaults cannot express.
Check your work
What a one-line class with val properties gives you. A full class with read-only properties
and a constructor — no fields, getters or boilerplate.
How you create an instance. By calling the class like a function — no new.
What val/var/nothing on a constructor parameter means. Read-only property, mutable property,
or a construction-only argument that is not kept.
Why constructing with named arguments is idiomatic. Many-field objects read like a labelled form and cannot be built in the wrong order.
What an init block is for. Running code at construction — typically validation with
require.
What a computed property is. A property with a get() and no stored value, recalculated on each
access — right for derived values.
What a method is. A function belonging to the class, acting on the object.
Why default arguments usually beat a secondary constructor. They express alternative construction more cleanly; reach for a secondary constructor only for genuinely different logic.
Practice
- Write
class Customer(val name: String, val email: String)and create one. Print both properties. - Add a defaulted
cityandisPremium, and construct an instance setting onlyisPremiumby name. - Change one property to
var, reassign it after creation, and confirm it changed. Change it back tovaland watch the reassignment fail. - Add an
initblock that validates the email contains@. Construct a valid and an invalid one; read the exception for the invalid. - Write an
Orderclass with a computedtotalPaiseproperty. Changeqty(make it avar) and confirmtotalPaiseupdates without you setting it. - Add a
formatTotal()method to the order. - Add a secondary constructor, then rewrite the same convenience using a default argument and decide which you prefer.
Official documentation
- Kotlin — Classes — Primary and secondary constructors,
initblocks. - Kotlin — Properties — Stored, computed, custom getters and setters.
- Kotlin — Requirements: require/check — Validating with
require.
Next: data classes — equality, copying, and printing for free.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship