You can’t compile a Kotlin class without a constructor. Write one yourself or let the compiler add an empty one, but something has to build the new object and give it its starting values.
The rules don’t change between the JVM and Android, and they hold on a Kotlin Multiplatform target too. If you’re coming from Java, you’ll probably compare all of this to constructor overloading. Fair enough. Kotlin’s primary and secondary forms were designed to replace that habit.
The rules are also still being tightened. Kotlin 2.0.20, released August 22, 2024, started warning developers that a data class built through a private constructor still generated a public copy() function. JetBrains is closing that visibility mismatch release by release (Kotlin documentation, kotlinlang.org).
What Is a Kotlin Constructor?

It’s a special member function, and its job is to create an instance and set up its state before anything else touches it.
In Java, each alternate way to build an object needs its own overloaded signature. Kotlin handles it differently. One class can hold a primary constructor, a few secondary ones and some init blocks, and you don’t have to repeat the parameter list across all of them.
For context, Kotlin is JetBrains’ statically typed language for the JVM, Android and multiplatform targets. Pretty much every class you write in it starts with a constructor call.
A few hard numbers help here:
- A class needs at least one constructor, primary or secondary, before Kotlin lets it compile (Kotlin documentation, kotlinlang.org)
- A constructor compiles down to a JVM method descriptor capped at 255 parameter slots, with only 254 open to an instance constructor since this claims one slot (Java Virtual Machine Specification, Oracle)
- Android’s View class ships with four separate constructors to cover code creation, XML inflation, and two levels of theme styling (Android Open Source Project documentation)
- A Kotlin data class needs at least one parameter in its primary constructor before the compiler generates equals, hashCode, or copy (Kotlin documentation, kotlinlang.org)
That last rule matters most for data classes. Kotlin data classes generate equals, hashCode and copy from the properties in the primary constructor, and nowhere else.
No constructor parameters, no generated methods.
How Does the Primary Constructor Work?

You write it in the class header, right after the class name. Kotlin treats it as the default way a class takes its starting values.
It can’t run code. If you need anything beyond assigning values, that logic goes in an init block or a property initializer.
Declaring Parameters in the Class Header
Parameters go in parentheses after the class name, comma-separated, same as a normal function signature.
The list takes the same kinds of types a regular function would, vararg parameters included.
- Nullable types, written with a trailing question mark
- Generic types, matched to the class’s own type parameters
- Vararg parameters, for an unknown number of values of the same type
You can drop the word constructor entirely. The keyword is optional as long as the primary constructor has no annotations and no visibility modifier.
Val and Var Parameters as Properties
Put val in front of a parameter and it becomes a read-only property, assigned once. With var you get a mutable property that code outside the class can change directly. I default to val and only switch when something actually needs to change later.
Leave both keywords off and the parameter is just a constructor argument. It’s gone the moment the constructor finishes.
You’ll see this all over Android’s Jetpack stack. A ViewModel usually takes its repository through the primary constructor, which lets dependency injection tools like Hilt supply it without any setter code.
How Does the Secondary Constructor Work?
Secondary constructors live in the class body and start with the constructor keyword. Each one gives you another way to build the same class.
If there’s a primary constructor, every secondary one has to delegate to it. It can do that directly or by going through another secondary constructor first.
- You can skip the primary constructor entirely and use only secondary ones
- Without a primary constructor, each secondary constructor must call the superclass constructor directly or delegate to another secondary constructor
- There’s no fixed limit on how many secondary constructors a class declares
Android’s View class is the textbook case. The extra constructors handle XML inflation and theme styling, and the core setup stays in one spot. That pattern turns up a lot in Android development, basically whenever one class needs several entry points.
Delegating to the Primary Constructor with this
Delegation can be direct or indirect, and Kotlin handles the two a bit differently at runtime.
The direct version is a secondary constructor calling this(...) and landing straight on the primary constructor. With indirect delegation, one secondary constructor calls this(...) on another secondary constructor, and that one passes the call further down the chain until it reaches the primary.
Loops aren’t allowed. Kotlin’s official language specification forbids two or more secondary constructors from delegating to each other in a circle, because the compiler would have no starting point to resolve.
Primary Constructor vs Secondary Constructor: What Is the Difference?

The primary constructor sits in the header and doesn’t delegate to anything. A secondary constructor sits in the body and always calls another constructor before it does its own work.
| Aspect | Primary Constructor | Secondary Constructor | Typical Use |
|---|---|---|---|
| Location | Class header | Class body | Standard setup vs alternate paths |
| Delegation | None required | Must call primary or another secondary constructor | Chained initialization |
| Default values | Supported directly | Rare, usually delegates instead | Reducing overloads |
| Common use case | Most everyday classes | Java interop, framework constraints | Legacy signatures, XML inflation |
Why have both? Java developers ask this a lot. You get flexibility without repetition.
Java’s overloading makes you write a separate signature for every combination of arguments. Joshua Bloch called this the telescoping constructor pattern in Effective Java, and anyone who has maintained a class with six overloads knows how it feels.
In Kotlin, a primary constructor with default values covers most of that on its own. It’s one of the reasons teams choosing between Kotlin or Java for an Android project bring up constructors at all.
What Is an Init Block and When Does It Run?
The primary constructor can’t hold code, so that code goes into an init block.
Kotlin counts init blocks and property initializers as part of the primary constructor. They aren’t separate code.
The ordering is where people slip. Init blocks and property initializers run top to bottom, in the order you wrote them. You can have more than one init block, and they follow that same order. A secondary constructor’s body only runs after all of them are done.
Plenty of Android codebases use an init block to start a repository call or log a screen view as soon as a ViewModel exists. That work happens before any secondary constructor logic runs.
This catches people from Java or C++ off guard. Those languages don’t separate constructor code from initializer code this cleanly.
How Do Default Parameter Values Replace Constructor Overloading?
You add a default right after the parameter type: an equals sign, then the fallback value.
If a caller leaves the parameter out, it gets the default. Named arguments go further and let them skip parameters in the middle of the list.
- One constructor signature instead of three or four overloaded ones
- Call sites stay readable with named arguments, even when several parameters have defaults
- Only one initialization path to maintain, so bugs have fewer places to hide
Subclassing AppCompatButton on Android is a good example. A Kotlin constructor with defaults for attrs and defStyleAttr fits in one line and replaces three or four Java-style constructors.
Collapsing overloads like that is simple code refactoring. Most IDEs will point out the opportunity for you.
If you’re still unsure about parameter order or where defaults should go, keep a Kotlin syntax reference open while you work.
Constructor Visibility Modifiers: Private, Protected, Internal and Public

Constructors take visibility modifiers the same way functions and properties do. The modifier decides who can call it.
| Modifier | Visible From | Typical Use |
|---|---|---|
| public | Anywhere the class itself is visible | Default, no modifier written |
| internal | Same module only | Library-internal helper classes |
| protected | The class and its subclasses | Base classes meant to be extended |
| private | Inside the class body only | Blocking outside instantiation |
Write no modifier and the constructor is public, just like the class.
A private constructor means nothing outside the class can call it directly.
Android’s Room persistence library restricts database creation in a similar way. A Room database class must be declared abstract, so the only way to create one is Room.databaseBuilder(), never a direct constructor call. By convention, that abstract class usually gets a companion object that hands out one shared instance.
That companion object setup is what most Kotlin singleton classes use under the hood. How it compares to Kotlin’s own object declaration is its own topic.
The modifier goes right before the constructor keyword. Something like class Database private constructor(…) keeps the parameter list as it is and blocks direct access.
Private Constructor vs Object Declaration for a Singleton: Which to Use?
With an object declaration you get a singleton and never call a constructor. A private constructor plus a companion object also gives you one instance, but there’s still a real constructor underneath.
If nothing outside needs to pass in a starting value, use the object declaration. That covers most cases in my experience.
Kotlin’s own documentation confirms that an object declaration’s initialization is thread-safe and happens on first access. You don’t write any locking code.
Square’s Retrofit library is a common example. The shared HTTP client usually lives in a plain object declaration, because it doesn’t need a Context or any configuration argument to exist.
The object declaration comes with almost no boilerplate. It’s one line, thread-safe out of the box, and Kotlin takes care of the lazy initialization. There are two catches. It can’t take a constructor argument when it’s created, and swapping it for a mock in tests usually needs extra tooling. That second one annoys me more than it should.
The private-constructor version gives you more control:
- It can take startup arguments, like a database name or a base URL
- You decide exactly when the instance gets created
- You write the singleton holder logic by hand
- Thread safety is easy to get wrong unless you use something like a synchronized block or a lazy delegate
So ask one thing. Does the singleton need an argument at the moment it’s built? If it doesn’t, it can be created up front, and a companion object can hand out that instance whenever it’s needed.
Constructor vs Builder Pattern: When to Use Each for Many Parameters
Most of the time, when a Java developer would reach for a builder, a Kotlin constructor with defaults does the job.
Named arguments already let callers skip the optional parameters they don’t need. A builder only earns its place when the class needs step-by-step validation, or when the object has to stay incomplete until it’s fully assembled.
Android’s AlertDialog.Builder ships with exactly two public constructors, one taking a Context and one taking a Context plus a theme resource, and handles everything else with chained setter-style calls (Android developer documentation).
| Aspect | Constructor with Defaults | Builder Pattern |
|---|---|---|
| Best for | A handful of optional parameters | Many optional parameters with validation |
| Readability | Named arguments at the call site | Chained method calls |
| Object state | Fully built in one call | Can stay incomplete until build() runs |
Use a builder when the class has to reject an invalid combination of parameters before the object exists. A plain constructor with defaults can’t do that by itself.
How Do You Write a Kotlin Constructor Step by Step?

Most classes only need the first two steps. The others matter once you need alternate creation paths or extra setup.
- Write the parameter list in the class header, on the same line as the class name.
- Add val or var only where the class needs a property. Leave it off anything you use once and throw away.
- Give optional parameters default values and put them after the required ones, so named arguments behave predictably.
- Move any setup logic into an init block, since the primary constructor can’t hold executable code.
- Add a secondary constructor only for a truly different creation path, and delegate it back with this(…).
- Check the visibility modifier. Keep it public unless outside code shouldn’t create instances.
IntelliJ IDEA and Android Studio will generate most of this through the Generate menu. It fills in constructor parameters from existing properties in a couple of clicks.
Going in this order also avoids the compile error beginners hit most often: referencing a parameter as if it were a property when there’s no val or var in front of it.
How Do Kotlin Constructors Work with Java Interoperability?

Java has no default arguments. So Java code can’t call a Kotlin constructor with defaults the way Kotlin code can.
The @JvmOverloads annotation fixes that. The compiler generates a real overload for every parameter carrying a default, starting from the last one and working backward (Kotlin documentation, kotlinlang.org).
Remember the custom Android views from earlier? They only inflate from XML because of this annotation. Without it, the XML-based constructor isn’t in the compiled class at all, and you get a crash at inflation time that takes a while to figure out the first time.
| Tool | Purpose | Typical Use |
|---|---|---|
| @JvmOverloads | Generates real Java overloads from default values | Custom views, public APIs called from Java |
| no-arg compiler plugin | Generates a synthetic zero-argument constructor | Classes read by reflection, like JPA entities |
Adding a No-Arg Constructor for Libraries Like Room or Gson
Libraries that rely on reflection often create the object first and fill in fields later. Kotlin’s usual constructor rules get in the way of that.
Take Gson. It deserializes JSON by creating an instance and then setting fields on it, which breaks for any class whose only constructor wants every property up front.
The no-arg compiler plugin handles this without changes to your class. It generates a synthetic zero-argument constructor for any class carrying an annotation you choose. That constructor is reachable only through reflection, never called directly from Kotlin or Java (Kotlin documentation, kotlinlang.org).
There’s also a kotlin-jpa variant that applies itself automatically to @Entity, @Embeddable, and @MappedSuperclass, the exact set Room and other JPA-based data layers rely on.
When Does a Kotlin Constructor Not Apply?

Some Kotlin constructs don’t fit the normal constructor model, either because nothing gets built or because the rules are much stricter.
| Construct | Constructor Behavior |
|---|---|
| Interface | Cannot declare a constructor at all |
| Enum class | One constructor call for every constant, even with no parameters written |
| Annotation class | No secondary constructors, no member functions, parameter types restricted |
| Data class | Cannot be abstract, open, sealed, or inner |
Interfaces can’t hold state or declare a constructor, so instantiating one directly won’t compile. Implement it in a real class first. That class owns the constructor.
A Kotlin enum class goes the other way. The compiler calls a constructor for every constant, even one with no parentheses or arguments.
Each constant compiles down to a call carrying two built-in values, its name and its ordinal position, plus whatever custom parameters the enum class declares (Kotlin documentation, kotlinlang.org).
Annotation classes are the strictest.
Kotlin’s language specification bans secondary constructors, member functions, and mutable properties on annotation classes entirely. Per Kotlin’s documentation on annotations (kotlinlang.org), the constructor can only accept these parameter types:
- Types that correspond to Java primitive types (Int, Boolean, Long, and so on)
- String
- Classes, passed with the ::class syntax
- Enum types
- Other annotation types
- Arrays of any of the above
Data classes have one more restriction on top of the minimum-parameter rule from earlier. A data class cannot be abstract, open, sealed, or inner, and that rules out several common designs before you’ve written any business logic.
FAQ on What Are Kotlin Constructors
Is a companion object the same as a constructor in Kotlin?
No, it’s not a constructor. It’s a singleton attached to a class, holding shared members and factory-style functions. People often pair one with a private constructor to control how instances get created, but the two do different jobs.
When should you use a secondary constructor instead of default values?
When the alternate path needs different logic, not just fewer parameters. Defaults cover optional data. Secondary constructors fit Java interop or legacy signatures that the primary constructor was never meant to accept.
Is constructor injection better than field injection for dependency injection in Kotlin?
In Kotlin, yes. Dependencies are explicit, you can keep them as val properties, and a missing one shows up at compile time. Field injection relies on a framework filling lateinit properties after creation, so the object is briefly incomplete.
What causes the “no value passed for parameter” error?
You called a constructor and left out a required parameter that has no default. Kotlin won’t guess. Pass the missing argument, add a default, or check the call with named arguments.
What Should You Fix First in a Kotlin Constructor?
Look for a public constructor with no default values and parameters that never became properties. That’s the first thing to fix. Open access plus missing defaults leads to exactly the overload sprawl the primary and secondary constructor system was built to remove.
Start by tightening visibility. After that, collapse the overloads into default values, and only add a secondary constructor if defaults really can’t cover a case.
Tightening visibility has a cost. A private constructor blocks the direct instantiation that unit tests often depend on, so plan for that.
You’re unlikely to get anywhere near the JVM’s 255-slot ceiling. Android’s View class needs only four constructors for everything it does, and a constructor becomes hard to read long before it hits that limit.
From here, a good next step is learning to build DSLs in Kotlin, which builds on the same object-assembly ideas and turns them into a domain-specific syntax.
- pip is not recognized in VS Code: Why It Happens and How to Fix It - October 1, 2026
- 10 Enterprise Server Virtualization Platforms for Modern Data Centers - October 1, 2026
- MySQL Cheat Sheet - September 30, 2026



