1. Overview
As data containers, Kotlin’s data classes, have implemented a few valuable methods by default, such as equals(), toString(), copy(), and so on.
In this tutorial, let’s explore how to instantiate a Kotlin data class using an empty constructor.
2. Introduction to the Problem
We’ve mentioned that as a data container, Kotlin’s data class brings many advantages. However, one requirement of a data class is that its primary constructor must have at least one parameter.
This means it doesn’t have a Java-like “default constructor” or “empty constructor”.
As data classes target the use cases of holding data, a data class may have quite a few parameters. Therefore, when we want to create an instance of a data class, we may pass dozens of parameters to the constructor. It’s a kind of inconvenience. We may miss the default constructor or empty constructor of a class.
In this tutorial, we’ll address a couple of ways to instantiate a data class using an empty constructor. Moreover, we’ll introduce Kotlin’s no-arg compiler plugin and discuss its use case.
For simplicity, we’ll use unit test assertions to verify the instances we create.
3. Adding an Empty Secondary Constructor
We’ve known that a Kotlin data class’s primary constructor must have at least one argument. Let’s see an example:
data class ArticleWithoutDefault(
var title: String,
var author: String,
var abstract: String,
var words: Long
)
In the data class above, the primary constructor has four arguments. We need to provide all of them if we want to instantiate the ArticleWithoutDefault class.
However, Kotlin allows us to create secondary constructors. For example, to instantiate the ArticleWithoutDefault class through an empty constructor, we can create one secondary constructor that calls the primary constructor with default values:
data class ArticleWithoutDefault(
var title: String,
var author: String,
var abstract: String,
var words: Long
) {
constructor() : this("dummy title", "dummy author", "dummy abstract", 0)
}
In this way, we can invoke the newly created constructor to get an instance of ArticleWithoutDefault. Of course, the instance will contain the initial values defined in the secondary constructor:
val myInstance = ArticleWithoutDefault()
assertThat(myInstance).isInstanceOf(ArticleWithoutDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("dummy title", "dummy author", "dummy abstract", 0L)
When we run the test, it passes. This means the instance is successfully created through the empty constructor.
4. Adding Default Values to the Primary Constructor
We’ve learned to add an empty secondary constructor that calls the primary one with initial property values. Actually, Kotlin allows us to set default values for function parameters. It works for constructors, too:
data class ArticleWithDefault(
var title: String = "default title",
var author: String = "default author",
var abstract: String = "",
var words: Long = 0L
)
As we can see in the data class above, we can define default values for the primary constructor’s parameters. Thus, we don’t need to create a secondary constructor. Instead, we can simply instantiate the data class using an empty constructor to apply all default values:
val myInstance = ArticleWithDefault()
assertThat(myInstance).isInstanceOf(ArticleWithDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("default title", "default author", "", 0L)
When a class constructor’s parameters have default values, it’s pretty flexible for creating instances of that class. For example, with the ArticleWithDefault data class, we can pass only title and words to the primary constructor and leave author and abstract with default values:
val myArticle = ArticleWithDefault(title="A Great Article", words=42L)
assertThat(myArticle).isInstanceOf(ArticleWithDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("A Great Article", "default author", "", 42L)
5. About the no-arg Compiler Plugin
Kotlin ships with a no-arg compiler plugin, which generates an additional empty constructor for classes with a specific annotation during compilation. It sounds like a great solution to our problem. However, it’s for other purposes. So next, let’s take a closer look at the no-arg plugin.
5.1. Configuring the no-arg Plugin
We need to do some configuration work on the Maven or Gradle side to enable the no-arg plugin. In this tutorial, we’ll take Maven as an example.
Let’s add the plugin to the
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<compilerPlugins>
<plugin>no-arg</plugin>
</compilerPlugins>
<pluginOptions>
<option>no-arg:annotation=com.baeldung.kotlin.emptyConstructorOfDataCls.NoArg</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
In the configuration above, the
Next, let’s create the annotation class.
5.2. Creating the @Noarg Annotation and Applying It to a Data Class
First, let’s create the NoArg annotation under the defined package:
package com.baeldung.kotlin.emptyConstructorOfDataCls
annotation class NoArg
Next, let’s create a new data class and annotate it as @NoArg:
@NoArg
data class Person(var firstName: String = "a nice name", var midName: String?, var lastName: String, var age: Int)
As the code above shows, our Person class has a primary constructor with four arguments. The firstName parameter has a default value. Further, except for midName, all parameters are not-null.
Now, if we compile the class, we may expect the no-arg plugin to create an empty constructor for us so that we can instantiate our Person class through something like val person = Person().
Next, let’s see if it works as expected.
5.3. Calling the Empty Constructor Directly From Kotlin
First, let’s call the empty constructor in a test method:
val person = Person()
However, when we compile the code, we’ll see it doesn’t compile:
[INFO] ...
[INFO] --- kotlin-maven-plugin:1.6.0:test-compile (test-compile) @ core-kotlin-lang-oop-2 ---
[INFO] Applied plugin: 'no-arg'
[ERROR] /.../EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'midName'
[ERROR] /.../EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'lastName'
[ERROR] /.../EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'age'
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
The build log above shows that the no-arg plugin has already been enabled. But why does it still complain about the constructor parameters? This is because the generated constructor is synthetic. In other words, it can’t be directly called from Java or Kotlin. So, it’s mainly for the reflection API.
5.4. Calling the Empty Constructor Using the Kotlin Reflection API
Next, let’s call the empty constructor using Kotlin reflection:
val person = Person::class.createInstance()
This time, the code compiles. However, when we execute the test method, the test fails:
ERROR!
java.lang.IllegalArgumentException: Class should have a single no-arg constructor: class com.baeldung.kotlin.emptyConstructorOfDataCls.Person
at com.baeldung.kotlin.emptyConstructorOf...(EmptyConstructorOfDataClsUnitTest.kt:55)
This is because Kotlin’s reflection API works with Kotlin code elements. The synthetic empty constructor is not a part of the Kotlin code. Thus, the empty constructor that the no-arg plugin created cannot be recognized by the Kotlin reflection API.
5.5. Calling the Empty Constructor Using the Java Reflection API
Finally, let’s call the empty constructor using the Java Reflection API:
val myInstance = Person::class.java.getConstructor().newInstance()
assertThat(myInstance).isInstanceOf(Person::class.java)
.extracting("firstName", "midName", "lastName", "age").containsExactly(null, null, null, 0)
When we execute the test above, it passes. Therefore, only Java reflection can use the synthetic constructor generated by the no-arg plugin.
Moreover, let’s revisit the parameters of Person‘s primary constructor:
var firstName: String = "a nice name",
var midName: String?,
var lastName: String,
var age: Int
The assertion in the test indicates that even if the empty constructor can be used by Java reflection, the created instance doesn’t follow the Kotlin class’s contract:
- Kotlin’s not-null properties such as firstName and lastName can hold a null value anyway.
- Default values of parameters, such as the one we specified for firstName, are not applied, either.
Basically, the no-arg compiler plugin is only useful for library development. For example, Java Persistence API (JPA) instantiates a class through its empty constructor. This plugin allows JPA to instantiate a Kotlin class, although no empty constructor is defined.
6. Conclusion
In this article, we’ve explored two approaches to instantiate a Kotlin data class using an empty constructor.
Further, we’ve discussed the no-arg compiler plugin. We should note that the empty constructor generated by the no-arg plugin can only be used by Java reflection.
As always, the full source code used in the article can be found over on GitHub.