Why is this an issue?

It is a common pattern to validate required preconditions at the beginning of a function or block. There are two different kinds of preconditions:

The Kotlin standard library provides the functions check(), require(), checkNotNull() and requireNotNull() for this purpose. They should be used instead of directly throwing an IllegalArgumentException or an IllegalStateException.

What is the potential impact?

Readability and Understanding

This change makes it easier to understand the code because the semantics of check(), require(), checkNotNull() and requireNotNull(), as well as the fact that this is a preconditions check, are evident to the reader. When developers share common standards and idioms, they need to spend less effort understanding each other’s code.

Code Redundancy

Using a built-in language feature or a standard API is always better than a custom implementation, because the reimplementation of something that already exists is unnecessary.

Consistency

When check(), require(), checkNotNull() and requireNotNull() are used in an idiomatic way, there is more consistency in what kind of exception is thrown in which situation.

How to fix it

Replace

With

if (!condition) throw IllegalArgumentException()

require(condition)

if (reference == null) throw IllegalArgumentException()

requireNotNull(reference)

require(reference != null)

requireNotNull(reference)

if (!condition) throw IllegalStateExceptionException()

check(condition)

if (reference == null) throw IllegalStateException()

checkNotNull(reference)

check(reference != null)

checkNotNull(reference)

throw IllegalStateException()

error()

A constructor function for the exception message can be provided as an optional argument for check(), require(), checkNotNull() and requireNotNull(). This means the message is constructed only if the exception is thrown. For the error() function, an optional error message parameter can be provided directly. That is, without a parameter, because an exception is unconditionally thrown by error().

Code examples

Noncompliant code example

fun argumentPreconditions(argument: Int?, limit: Int?) {
    if (argument == null) throw IllegalArgumentException() // Noncompliant, replace with requireNotNull
    require(limit != null)  // Noncompliant, replace with requireNotNull
    if (argument < 0) throw IllegalArgumentException()  // Noncompliant, replace with require
    if (argument >= 0) throw IllegalArgumentException("Argument < $limit") // Noncompliant, replace with require
}

Compliant solution

fun argumentPreconditions(argument: Int?, limit: Int?) {
    requireNotNull(argument) // Compliant
    requireNotNull(limit) // Compliant
    require(argument >= 0) // Compliant
    require(argument < limit) {"Argument < $limit"} // Compliant
}

Noncompliant code example

fun statePreconditions() {
    if (state == null) throw IllegalStateException() // Noncompliant, replace with checkNotNull
    check(ioBuffer != null)  // Noncompliant, replace with checkNotNull
    if (state < 0) throw IllegalStateException()  // Noncompliant, replace with check
    if (state == 42) throw IllegalStateException("Unknown question") // Noncompliant, replace with check

    when(state) {
        0..10 -> processState1()
        11..1000 -> processState2()
        else -> throw IllegalStateException("Unexpected state $state") // Noncompliant, replace with error
    }
}

Compliant solution

fun statePreconditions() {
    checkNotNull(state) // Compliant
    checkNotNull(ioBuffer) // Compliant
    check(state >= 0) // Compliant
    check(state != 42) {"Unknown question"} // Compliant

    when(state) {
        0..10 -> processState1()
        11..1000 -> processState2()
        else -> error("Unexpected state $state") // Compliant
    }
}

Resources

Documentation

Articles & blog posts