In programming, it's common to write an if statement to check a precondition, for example:
fun doSomething(value: Int) {
if (value < 0) {
throw IllegalArgumentException("value cannot be negative")
}
// do something
}
Kotlin offers some helper functions to make your code more concise.
The require
function
We can rewrite the code above in the following way:
fun doSomething(value: Int) {
require(value > 0) { "value cannot be negative" }
// do something
}
The require
function helps validate function arguments. If the condition isfalse, it will throw an IllegalArgumentException
with the message provided in the lambda.
The requireNotNull
function
This function has a self-explanatory name. It will check if the value is null; if it's false, it will throw an IllegalArgumentException with the message provided in the lambda. See the example:
fun plusOne(number: Int?) : Int {
requireNotNull(number) { "number cannot be null" }
return number + 1
}
The smart cast works in this scenario. This is why we can sum the number in the second line without worrying about null values.
The check
function
The check function has the same signature as the require function, but it will throw an IllegalStateException
instead. This function is not used to validate parameters. You should use it to validate the internal state of a class.
See this example:
class Queue {
private val queue = ArrayDeque<String>(10)
fun enqueue(id: String) {
check(queue.size == 10) { "Queue is full" }
queue.push(id)
}
fun dequeue(): String {
check(queue.isEmpty()) { "Queue is empty" }
return queue.pop()
}
}
If any of the validations fail, an IllegalStateException
will be thrown.
The checkNotNull
function
This works the same way as the requireNotNull
function, but it throws an IllegalStateException
instead of an IllegalArgumentException
. This function is to be used to validate null inside the state of a class, just like the check function. Example:
data class User(
val id: String,
val name: String,
val email: String,
)
interface UserRepository {
fun findById(): User?
}
interface EmailService {
fun sendEmail(email: String, message: String)
}
class SendEmailUseCase(
private val userRepository: UserRepository,
private val emailService: EmailService,
) {
fun sendMessage(message: String) {
val user = userRepository.findById()
checkNotNull(user) { "User not found" }
emailService.sendEmail(user.email, message)
}
}
The error function
The error function always throws an IllegalStateException when it's called. This is an example of the error function in action. It's common to see it used together with the Elvis operator.
class SendEmailUseCase(
private val userRepository: UserRepository,
private val emailService: EmailService,
) {
fun sendMessage(message: String) {
val user = userRepository.findById()
?: error("User not found")
emailService.sendEmail(user.email, message)
}
}
Another common usage is in the when expression:
fun processMessage(message: Message): String {
return when (message.type) {
"info" -> "INFO: ${message.message}"
"warning" -> "WARNING: ${message.message}"
"error" -> "ERROR: ${message.message}"
else -> error("Unknown message type ${message.type}")
}
}
Conclusion As we can see, Kotlin offers some functions to validate preconditions that can make your code less verbose and more idiomatic. The functions require and check have the same signature but throw different exceptions and have different purposes.