Question

Kotlin Data Class Default Constructor and Optional Parameters in Android

kotlinandroid

Question

In Kotlin for Android, I have a data class with many properties, and I want to create an object without passing every constructor argument, similar to how an empty constructor is often used in a Java POJO.

Here is the data class:

data class Activity(
    var updated_on: String,
    var tags: List<String>,
    var description: String,
    var user_id: List<Int>,
    var status_id: Int,
    var title: String,
    var created_at: String,
    var data: HashMap<*, *>,
    var id: Int,
    var counts: LinkedTreeMap<*, *>,
)

I would like to use it like this:

val activity = Activity()
activity.title = "New Computer"
sendToServer(activity)

But right now, Kotlin requires all constructor arguments to be passed when creating the object, so I end up doing this:

val activity = Activity(
    null,
    null,
    null,
    null,
    null,
    "New Computer",
    null,
    null,
    null,
    null
)
sendToServer(activity)

How can this be simplified in Kotlin?

Short Answer

By the end of this page, you will understand how Kotlin handles constructors in data classes, why all primary constructor values are required by default, and how to make object creation easier using default parameter values and nullable types when needed. You will also see cleaner alternatives such as named arguments and learn when an "empty constructor" is appropriate in Kotlin.

Concept

In Kotlin, a data class is designed to hold data and usually defines its properties directly in the primary constructor. If a constructor parameter has no default value, Kotlin requires you to provide it every time you create an object.

That is why this fails:

val activity = Activity()

Your class currently says that every property is required:

data class Activity(
    var updated_on: String,
    var tags: List<String>,
    var description: String,
    var user_id: List<Int>,
    var status_id: Int,
    var title: String,
    var created_at: String,
    var data: HashMap<*, *>,
    var id: Int,
    var counts: LinkedTreeMap<*, *>
)

Kotlin's usual solution is not to add a Java-style empty constructor manually. Instead, you give properties default values in the primary constructor. Then Kotlin can generate a no-argument style call like Activity().

Example:

data class Activity(
    var updated_on: String = "",
    var tags: List<String> = emptyList(),
    var description: String = "",
    var user_id: List<Int> = emptyList(),
    var status_id: Int = 0,
    var title: String = "",
    var created_at: String = "",
    var data: HashMap<*, *> = hashMapOf<Any?, Any?>(),
    var id: Int = 0,
    var counts: LinkedTreeMap<*, *> = LinkedTreeMap<Any?, Any?>()
)

Now this works:

val activity = Activity()
activity.title = "New Computer"

If a property is allowed to be missing, you can also make it nullable:

var description: String? = null

This matters in real programming because Kotlin encourages you to model data clearly:

  • Required values stay non-null and required.
  • Optional values get defaults or become nullable.
  • Object creation becomes safer and easier to read.

This leads to cleaner Android code, safer API models, and fewer bugs caused by accidental null values.

Mental Model

Think of a Kotlin data class constructor like a form you must fill out.

  • If every field is marked required, you must complete them all before submitting.
  • If some fields have pre-filled default values, you can leave them alone and only change what matters.
  • If some fields are nullable, it means "this field can be left blank."

So instead of building a Java-style empty object first and filling it later, Kotlin prefers one of these approaches:

  • create an object with sensible defaults
  • provide only the values you need
  • explicitly mark optional fields as nullable

That makes your data model more honest and easier to reason about.

Take Quiz

Syntax and Examples

Core syntax

Use default parameter values in the primary constructor:

data class Activity(
    var updatedOn: String = "",
    var tags: List<String> = emptyList(),
    var description: String = "",
    var userIds: List<Int> = emptyList(),
    var statusId: Int = 0,
    var title: String = "",
    var createdAt: String = "",
    var data: HashMap<String, Any> = hashMapOf(),
    var id: Int = 0
)

Now you can create it with no arguments:

val activity = Activity()
activity.title = "New Computer"

Using named arguments

Kotlin also lets you set only what you care about at creation time:

val activity = Activity(title = "New Computer")

This is usually cleaner than creating an empty object and mutating it later.

Using nullable properties when values may be absent

If a field may genuinely be missing, make it nullable:

data class Activity(
    var updatedOn: String? = null,
    var tags: List<String> = emptyList(),
    var description: String? = null,
    var userIds: List<Int> = emptyList(),
    var statusId: Int = 0,
    var title: String = "",
    var createdAt: String? = null,
    var data: HashMap<String, Any> = hashMapOf(),
    var id: Int = 0
)

Better version of your example

data class Activity(
    var updated_on: String? = null,
    var tags: List<String> = emptyList(),
    var description: String? = null,
    var user_id: List<Int> = emptyList(),
    var status_id: Int = 0,
    var title: String = "",
    var created_at: String? = null,
    var data: HashMap<Any?, Any?> = hashMapOf(),
    var id: Int = 0,
    var counts: LinkedTreeMap<Any?, Any?> = LinkedTreeMap()
)

Usage:

val activity = Activity(title = "New Computer")
sendToServer(activity)

This is concise, readable, and idiomatic Kotlin.

Step by Step Execution

Consider this example:

data class Activity(
    var title: String = "",
    var statusId: Int = 0,
    var description: String? = null
)

val activity = Activity(title = "New Computer")

What happens step by step

  1. Kotlin sees the Activity constructor has default values for all properties.

  2. You call:

    Activity(title = "New Computer")
    
  3. Kotlin assigns title to "New Computer".

  4. Kotlin uses the default value 0 for statusId.

  5. Kotlin uses the default value null for description.

  6. The final object becomes:

Activity(title="New Computer", statusId=0, description=null)

Another trace

val activity = Activity()
activity.title = "New Computer"

Step by step:

  1. Activity() creates an object using all default values.
  2. So initially it is:
Activity(title="", statusId=0, description=null)
  1. Then activity.title = "New Computer" updates the title property.
  2. The object is now:
Activity(title="New Computer", statusId=0, description=null)

This works only because every constructor parameter had a default value.

Real World Use Cases

Default constructor-like behavior in Kotlin is useful in many practical situations:

  • Form models in Android apps
    • Start with empty values and fill fields as the user types.
  • API request bodies
    • Create a request object with a few known fields and leave the rest as defaults.
  • JSON parsing models
    • Some fields may be missing in server responses, so defaults or nullable fields help.
  • Local state objects
    • UI state in ViewModels often starts with empty strings, empty lists, or null.
  • Testing
    • Build objects quickly without passing every field each time.

Example in an Android ViewModel:

val draft = Activity(title = "New Computer")
repository.sendToServer(draft)

This is much clearer than passing ten null values.

Take Quiz

Real Codebase Usage

In real projects, developers usually combine default values with a few common Kotlin patterns.

1. Named arguments

Instead of relying on positional arguments:

val activity = Activity("", emptyList(), "", emptyList(), 0, "New Computer", "", hashMapOf(), 0, LinkedTreeMap())

developers prefer:

val activity = Activity(title = "New Computer")

This is easier to read and safer.

2. Validation before sending data

A model may allow defaults, but the app still validates required fields:

if (activity.title.isBlank()) {
    throw IllegalArgumentException("Title is required")
}

3. copy() for updates

Data classes support copy(), which is heavily used in real codebases:

val original = Activity(title = "Draft")
val updated = original.copy(title = "Published")

This is especially common in state management.

4. Safe defaults for collections

Developers usually prefer:

  • emptyList() instead of null
  • emptyMap() or hashMapOf() instead of null

This reduces null checks.

5. Separate request/response models

In larger apps, request models are often different from UI models. For example:

  • ActivityDraft for user input
  • ActivityRequest for API submission
  • ActivityResponse for server data

This avoids one class trying to represent every possible state.

Common Mistakes

1. Using null with non-nullable types

This does not work:

data class Activity(
    var title: String,
    var id: Int
)

val activity = Activity(null, null)

Why it fails:

  • String is not String?
  • Int is not Int?

Fix it by either:

  • making the property nullable, or
  • providing a real default value
data class Activity(
    var title: String = "",
    var id: Int = 0
)

2. Making everything nullable without a reason

Broken design:

data class Activity(
    var title: String? = null,
    var id: Int? = null
)

This may force null checks everywhere:

if (activity.title != null) {
    println(activity.title.length)
}

Prefer non-null properties with sensible defaults when possible.

3. Passing many positional arguments

Hard to read:

val activity = Activity("", emptyList(), "", emptyList(), 0, "New Computer", "", hashMapOf(), 0, LinkedTreeMap())

Better:

val activity = Activity(title = "New Computer")

4. Choosing poor defaults

A default should make sense.

  • "" for text fields may be fine
  • emptyList() is often better than null
  • 0 may or may not make sense for IDs depending on your app

5. Ignoring model meaning

If a field is truly required for valid business logic, do not hide that by making it optional unless necessary. Defaults improve convenience, but they should not weaken correctness.

Comparisons

ApproachHow it worksProsConsBest use
Required constructor parametersEvery field must be providedStrict and explicitVerbose for large modelsWhen all fields are truly required
Default parameter valuesConstructor parameters have fallback valuesClean, idiomatic Kotlin, allows Activity()Poor defaults can hide invalid dataMost common Kotlin solution
Nullable propertiesFields can be nullGood for truly optional dataMore null checksWhen missing values are valid
Secondary constructorAdd another constructor manuallyCan help interoperability in some casesUsually unnecessary in KotlinSpecial cases, Java/framework needs

Default values vs nullable properties

// Default values
data class User(val name: String = "")

// Nullable
data class User(val name: String? = null)

Choose default values when:

  • the property should usually exist
  • an empty value is acceptable

Choose nullable when:

  • the property may genuinely be absent
  • null has a real meaning

Kotlin style vs Java POJO style

Java often uses:

  • empty constructor
  • setters after construction

Kotlin usually prefers:

  • primary constructor
  • defaults
  • named arguments
  • immutable val properties when possible

That leads to more predictable objects.

Take Quiz

Cheat Sheet

Quick reference

Create a data class with default values

data class Activity(
    var title: String = "",
    var id: Int = 0,
    var tags: List<String> = emptyList()
)

Call it with no arguments

val activity = Activity()

Set only one property using named arguments

val activity = Activity(title = "New Computer")

Make a field optional with null

var description: String? = null

Prefer these defaults

  • String -> ""
  • Int -> 0 if valid in your domain
  • List<T> -> emptyList()
  • MutableList<T> -> mutableListOf()
  • HashMap<K, V> -> hashMapOf()

Rules to remember

  • No default value => argument is required
  • Default value => argument becomes optional
  • Non-null type cannot receive null
  • Use named arguments to avoid long unreadable constructor calls
  • Use copy() to create modified versions of data class objects

Example

data class Activity(
    var title: String = "",
    var description: String? = null
)

val a1 = Activity()
val a2 = Activity(title = "New Computer")
val a3 = a2.copy(description = "Office setup")

FAQ

Can a Kotlin data class have an empty constructor?

Yes, effectively. If all constructor parameters have default values, you can call the class with no arguments using Activity().

Should I use null for every field to simulate Java behavior?

Usually no. Prefer sensible default values and only use nullable types when null has a real meaning.

What is the idiomatic Kotlin way to create an object with only one changed field?

Use default values plus named arguments, for example:

val activity = Activity(title = "New Computer")

Can I add a secondary constructor instead?

Yes, but it is usually unnecessary for this case. Default parameter values are the standard Kotlin solution.

Why does Kotlin not allow null for String or Int automatically?

Because Kotlin distinguishes between nullable and non-nullable types to reduce null-related bugs.

Is Activity() or Activity(title = "New Computer") better?

Activity(title = "New Computer") is usually better because it expresses intent clearly at creation time.

Should data class properties be var or val?

Use val when the value should not change after creation. Use var only when mutation is necessary.

Take Quiz

Mini Project

Description

Build a small Kotlin model for creating and sending a draft support ticket. The goal is to practice defining a data class with default values so you can create objects without passing every field manually. This mirrors real Android and backend request models where some fields start empty and only a few are filled before submission.

Goal

Create a Kotlin data class that can be instantiated with no arguments or with only selected fields using named arguments.

Requirements

  • Define a data class for a support ticket with at least five properties.
  • Give every property a sensible default value.
  • Create one empty ticket and update one field after creation.
  • Create another ticket using named arguments.
  • Print both tickets to verify the values.
Take Quiz

Keep learning

Related questions

View more