DEV Community

Dylan Maccora
Dylan Maccora

Posted on • Updated on • Originally published at maccoda.github.io

Object construction validation in Kotlin

Recently I was working with a bit of code where we had written validation logic in the constructor. This was simply validating that the input received from an API call matched what we required. We are currently migrating some of the code across from Java to Kotlin and whilst most of the time this is a straightforward process this validation one wasn't so smooth.

Let's say you have a FullName class that requires both a first and last name that cannot be empty. We might have a class something like the following:

public class FullName {
  private final String firstName;
  private final String lastName;

  public FullName(final String firstName, final String lastName) {
    if (StringUtils.isBlank(firstName) || StringUtils.isBlank(lastName)) {
      throw new InvalidDataException("Names must not be blank");
    }
    this.firstName = firstName;
    this.lastName = lastName;
  }
  // getters...
}
Enter fullscreen mode Exit fullscreen mode

We want to move this across to a new FullName Kotlin class instead, below are some of the options we managed create. Each have different styles and benefits so it is up to you which you prefer.

For these examples I am assuming that firstName and lastName didn't have to add a null check. If this is not the case for you simply change the type to a nullable type and perform the check using safe calls or similar. Do this so that you do not get an exception throw from the standard library which is essentially a null pointer exception.

Using an init block

class FullName(firstName: String, lastName: String) {
    val firstName: String
    val lastName: String
    init {
        if (firstName.isBlank() || lastName.isBlank()) {
            throw RuntimeException("Name cannot be blank")
        }
        this.firstName = firstName
        this.lastName = lastName
    }
}
Enter fullscreen mode Exit fullscreen mode

Try this in the playground

Pros

  • This has the same interface as the previous Java class so all calling code should fit nicely

Cons

  • We haven't been able to convert to a data class where it may have been similar to one in Java. This is because a data class requires a public field being present in the primary constructor. This can easily be overcome by using your IDE to generate the equals/hashcode and toString functions you may have wanted.

Use a static constructor

Using static constructors is a recommended practice as per Effective Java, so one alternative is to put the validation logic into such a function so the class becomes simple. In our above I believe it was done in the way above to match the Spring MVC convention.

data class FullName private constructor(val firstName: String, val lastName: String) {

    companion object {
        fun of(firstName: String, lastName: String): FullName {
            if(firstName.isBlank() || lastName.isBlank()) {
                throw RuntimeException("Name cannot be blank")
            }
            return FullName(firstName, lastName)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Try this in the playground

Pros

  • We are now able to use the data class
  • The class itself doesn't have a 'smart' constructor
  • We are able to make all construction through the static method using private constructor

Cons

  • We have now changed the signature of the class for the callers of the existing class

Using Invoke to get both

I have not been able to test how this works with Java interoperability but this option will give you the best of the above two solutions.

data class FullName private constructor(val firstName: String, val lastName: String) {

    companion object {
        operator fun invoke(firstName: String, lastName: String): FullName {
            if(firstName.isBlank() || lastName.isBlank()) {
                throw RuntimeException("Name cannot be blank")
            }
            return FullName(firstName, lastName)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Try this in the playground

Top comments (0)