In Kotlin, a class is the only way to create a custom type. If you need to represent a Person, a BankAccount, a NetworkRequest, or any other domain concept — you define a class.
Kotlin classes are designed to be concise. The same structures that require dozens of lines in Java can be expressed in a handful of lines in Kotlin, without sacrificing readability or capability. This conciseness is not just cosmetic — it means less code to write, less code to read, and fewer places for bugs to hide.
The Structure of a Kotlin Class
A Kotlin class can contain:
- Properties — data the class holds, with optional custom access logic
- Primary Constructor — the main way to create instances and initialize properties
- Functions — behavior the class exposes
- Initialization Blocks — code that runs during construction
- Secondary Constructors — alternative construction paths
+------------------------------------+
| Kotlin Class |
| |
| Properties |
| +----------------------------+ |
| | val name: String | |
| | var weight: Double | |
| +----------------------------+ |
| |
| Primary Constructor |
| +----------------------------+ |
| | class Foo(val x: Int) | |
| +----------------------------+ |
| |
| Functions / Init Blocks |
| +----------------------------+ |
| | fun doThing() { ... } | |
| | init { ... } | |
| +----------------------------+ |
+------------------------------------+
Properties
Properties are the data members of a Kotlin class. They are more powerful than simple Java fields — each property can have custom logic for reading and writing its value via getter and setter functions.
Basic Properties
class Person {
val name: String = "Jim" // Immutable property
var weightLbs: Double = 0.0 // Mutable property
}
val p = Person()
val name = p.name // Accessing a property reads its value
p.weightLbs = 220.0 // Assigning to a var property writes its value
Properties are accessed with dot notation — no getName() or setWeight() methods needed. Kotlin generates appropriate accessor methods for Java interoperability under the hood, but your Kotlin code never sees them.
Custom Getters and Setters
Sometimes a property's stored value should differ from the value you read or write. Kotlin lets you define custom getter and setter logic directly on the property definition.
class Person {
val name: String = "Jim"
var weightLbs: Double = 0.0
var weightKilos: Double
get() = weightLbs / 2.2 // Custom getter: converts lbs to kg
set(value) {
weightLbs = value * 2.2 // Custom setter: converts kg to lbs
}
}
val p = Person()
p.weightLbs = 220.0
// Accessing weightKilos runs the getter, returns ~100.0
val kilos = p.weightKilos
// Assigning to weightKilos runs the setter, updates weightLbs
p.weightKilos = 50.0
// weightLbs is now 110.0
val lbs = p.weightLbs
Custom getters and setters allow you to expose a computed or converted view of data without storing redundant values. In this example, only weightLbs is stored as a backing field — weightKilos is derived on demand.
This is a powerful encapsulation tool: from the outside, both look like simple properties. The conversion logic is hidden inside the class.
Primary Constructor
Every Kotlin class can have a primary constructor — the main constructor that defines the parameters required to create an instance and can initialize properties.
The primary constructor is declared as part of the class header, after the class name. The constructor keyword is optional when there are no annotations or visibility modifiers:
// Verbose form with explicit constructor keyword
class Person constructor(name: String, weightLbs: Double) {
val name: String = name
var weightLbs: Double = weightLbs
var weightKilos: Double
get() = weightLbs / 2.2
set(value) {
weightLbs = value * 2.2
}
}
This is already more concise than Java, but Kotlin goes further. You can move the property declaration directly into the constructor parameter list using val or var:
// Concise form — properties declared in the constructor header
class Person(val name: String, var weightLbs: Double) {
var weightKilos: Double
get() = weightLbs / 2.2
set(value) {
weightLbs = value * 2.2
}
}
When you write val name: String in the constructor, Kotlin automatically:
- Declares a property named
nameof typeString - Assigns the constructor argument to that property
- Makes it immutable (because
val)
This eliminates the boilerplate assignment this.name = name that Java requires.
Creating Instances
val p = Person("Bob", 176.0)
val name = p.name // "Bob"
val lbs = p.weightLbs // 176.0
val kilos = p.weightKilos // ~80.0 (176.0 / 2.2)
Kotlin does not use a new keyword — you call the class name as if it were a function.
Constructor Parameters vs Properties
It is worth understanding the difference between a constructor parameter and a property:
- A parameter with
valorvarin the constructor header becomes a property — it is accessible on instances viap.name - A parameter without
valorvaris a constructor-only parameter — it exists only during construction and is not accessible afterward
class FullName(val firstName: String, val lastName: String, val title: String = "")
// title has a default value — it is optional
val person1 = FullName("John", "Doe")
val person2 = FullName("Jane", "Doe", "Dr.")
Default parameter values in constructors eliminate the need for multiple constructor overloads that differ only in optional parameters.
Comparing Kotlin to Java
Consider a simple class that stores a name and a weight with a computed conversion property. In Java:
// Java — verbose
public class Person {
private final String name;
private double weightLbs;
public Person(String name, double weightLbs) {
this.name = name;
this.weightLbs = weightLbs;
}
public String getName() { return name; }
public double getWeightLbs() { return weightLbs; }
public void setWeightLbs(double weightLbs) { this.weightLbs = weightLbs; }
public double getWeightKilos() { return weightLbs / 2.2; }
public void setWeightKilos(double kilos) { this.weightLbs = kilos * 2.2; }
}
In Kotlin:
// Kotlin — concise
class Person(val name: String, var weightLbs: Double) {
var weightKilos: Double
get() = weightLbs / 2.2
set(value) { weightLbs = value * 2.2 }
}
The Kotlin version has fewer lines, less repetition, and expresses the same intent more clearly. Both are fully equivalent in behavior.
Data Classes
For classes that exist purely to hold data — transfer objects, API models, database entities — Kotlin provides data classes:
data class User(val id: Int, val name: String, val email: String)
A data class automatically generates:
equals()— structural equality based on all propertieshashCode()— consistent withequals()toString()— readable representation:User(id=1, name=Alice, email=alice@example.com)copy()— creates a modified copy without mutating the original
val user1 = User(1, "Alice", "alice@example.com")
val user2 = user1.copy(email = "newemail@example.com")
println(user1 == user2) // false (different email)
println(user1.toString()) // User(id=1, name=Alice, email=alice@example.com)
Data classes are ideal for representing data in MVVM architecture — API response models, ViewState objects, and domain entities.
Summary
| Feature | Description |
|---|---|
| Properties | Data members with optional custom getter/setter logic |
val property | Immutable — cannot be reassigned |
var property | Mutable — can be reassigned |
| Custom getter | Computed value derived from backing data |
| Custom setter | Convert or validate before storing |
| Primary constructor | Declare and initialize properties in the class header |
val/var in constructor | Shorthand that declares a property and assigns it |
| Data class | Auto-generates equals, hashCode, toString, copy |
Kotlin classes are designed to be expressive and concise. The primary constructor with inline property declarations eliminates the Java constructor boilerplate, and custom getters and setters provide clean encapsulation without the ceremony of explicit accessor methods.
In Kotlin, a class should say exactly what it needs to say — and nothing more.