Skip to main content
Version: Next

Standard-library Rule Set

Rule Set ID: standard-library

The Standard Library (stdlib) ruleset provides rules that assert the correct usage of the classes in the stdlib.

AlsoCouldBeApply

Detects when an also block contains only it-started expressions.

By refactoring the also block to an apply block makes it so that all its can be removed thus making the block more concise and easier to read.

Active by default: No

Noncompliant Code:

Buzz().also {
it.init()
it.block()
}

Compliant Code:

Buzz().apply {
init()
block()
}

// Also compliant
fun foo(a: Int): Int {
return a.also { println(it) }
}

ArrayPrimitive

Using Array<Primitive> leads to implicit boxing and performance hit. Prefer using Kotlin specialized Array Instances.

As stated in the Kotlin documentation Kotlin has specialized arrays to represent primitive types without boxing overhead, such as IntArray, ByteArray and so on.

Active by default: Yes - Since v1.2.0

Requires Type Resolution

Noncompliant Code:

fun function(array: Array<Int>) { }

fun returningFunction(): Array<Double> { }

Compliant Code:

fun function(array: IntArray) { }

fun returningFunction(): DoubleArray { }

CharArrayToStringCall

Reports CharArray.toString() calls that do not return the expected result.

Active by default: Yes - Since v2.0.0

Requires Type Resolution

Noncompliant Code:

val s = ""
val charArray = "hello😅".toCharArray()

println("$s$charArray") // [C@4f023edb
println(charArray.toString()) // [C@4f023edb
println(s + charArray) // [C@4f023edb

Compliant Code:

println("$s${charArray.concatToString()}") // hello😅
println(charArray.concatToString()) // hello😅
println(s + charArray.concatToString()) // hello😅

CouldBeSequence

Long chains of collection operations will have a performance penalty due to a new list being created for each call. Consider using sequences instead. Read more about this in the documentation

Active by default: No

Requires Type Resolution

Configuration options:

  • allowedOperations (default: 2)

    The maximum number of allowed chained collection operations.

Noncompliant Code:

listOf(1, 2, 3, 4).map { it*2 }.filter { it < 4 }.map { it*it }

Compliant Code:

listOf(1, 2, 3, 4).asSequence().map { it*2 }.filter { it < 4 }.map { it*it }.toList()

listOf(1, 2, 3, 4).map { it*2 }

DontDowncastCollectionTypes

Down-casting immutable types from kotlin.collections should be discouraged. The result of the downcast is platform specific and can lead to unexpected crashes. Prefer to use instead the toMutable<Type>() functions.

Active by default: No

Requires Type Resolution

Noncompliant Code:

val list : List<Int> = getAList()
if (list is MutableList) {
list.add(42)
}

(list as MutableList).add(42)

Compliant Code:

val list : List<Int> = getAList()
list.toMutableList().add(42)

DoubleMutabilityForCollection

Using var when declaring a mutable collection or value holder leads to double mutability. Consider instead declaring your variable with val or switching your declaration to use an immutable type.

By default, the rule triggers on standard mutable collections, however it can be configured to trigger on other types of mutable value types, such as MutableState from Jetpack Compose.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Aliases: DoubleMutability

Configuration options:

  • mutableTypes (default: ['kotlin.collections.MutableList', 'kotlin.collections.MutableMap', 'kotlin.collections.MutableSet', 'java.util.ArrayList', 'java.util.LinkedHashSet', 'java.util.HashSet', 'java.util.LinkedHashMap', 'java.util.HashMap'])

    Define a list of mutable types to trigger on when defined with var.

Noncompliant Code:

var myList = mutableListOf(1,2,3)
var mySet = mutableSetOf(1,2,3)
var myMap = mutableMapOf("answer" to 42)

Compliant Code:

// Use val
val myList = mutableListOf(1,2,3)
val mySet = mutableSetOf(1,2,3)
val myMap = mutableMapOf("answer" to 42)

// Use immutable types
var myList = listOf(1,2,3)
var mySet = setOf(1,2,3)
var myMap = mapOf("answer" to 42)

ForEachOnRange

Using the forEach method on ranges has a heavy performance cost. Prefer using simple for loops.

Benchmarks have shown that using forEach on a range can have a huge performance cost in comparison to simple for loops. Hence, in most contexts, a simple for loop should be used instead. See more details here: Exploring Kotlin Hidden Costs - Part 1 Exploring Kotlin Hidden Costs - Part 2 Exploring Kotlin Hidden Costs - Part 3

To solve this code smell, the forEach usage should be replaced by a for loop.

Active by default: Yes - Since v1.0.0

Noncompliant Code:

(1..10).forEach {
println(it)
}
(1 until 10).forEach {
println(it)
}
(10 downTo 1).forEach {
println(it)
}

Compliant Code:

for (i in 1..10) {
println(i)
}

IteratorHasNextCallsNextMethod

Verifies implementations of the Iterator interface. The hasNext() method of an Iterator implementation should not have any side effects. This rule reports implementations that call the next() method of the Iterator inside the hasNext() method.

Active by default: Yes - Since v1.2.0

Noncompliant Code:

class MyIterator : Iterator<String> {

override fun hasNext(): Boolean {
return next() != null
}
}

IteratorNotThrowingNoSuchElementException

Reports implementations of the Iterator interface which do not throw a NoSuchElementException in the implementation of the next() method. When there are no more elements to return an Iterator should throw a NoSuchElementException.

See: https://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html#next()

Active by default: Yes - Since v1.2.0

Noncompliant Code:

class MyIterator : Iterator<String> {

override fun next(): String {
return ""
}
}

Compliant Code:

class MyIterator : Iterator<String> {

override fun next(): String {
if (!this.hasNext()) {
throw NoSuchElementException()
}
// ...
}
}

MapGetWithNotNullAssertionOperator

Reports calls of the map access methods map[] or map.get() with a not-null assertion operator !!. This may result in a NullPointerException. Preferred access methods are map[] without !!, map.getValue(), map.getOrDefault() or map.getOrElse().

Based on an IntelliJ IDEA inspection MapGetWithNotNullAssertionOperatorInspection.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

val map = emptyMap<String, String>()
map["key"]!!

val map = emptyMap<String, String>()
map.get("key")!!

Compliant Code:

val map = emptyMap<String, String>()
map["key"]

val map = emptyMap<String, String>()
map.getValue("key")

val map = emptyMap<String, String>()
map.getOrDefault("key", "")

val map = emptyMap<String, String>()
map.getOrElse("key", { "" })

MissingUseCall

Prefer using the use function with Closeable or AutoCloseable. As use function ensures proper closure of Closable. It also properly handles exceptions if raised while closing the resource

Active by default: No

Requires Type Resolution

Configuration options:

  • ignoreClass (default: ['java.io.ByteArrayInputStream', 'java.io.ByteArrayOutputStream'])

    List of fully qualified class names that should be excluded from this rule (treated as not-closable).

Noncompliant Code:

val myCloseable = MyCloseable()
// do stuff with myCloseable

MyClosable().doStuff()

functionThatReturnsClosable().doStuff()

Compliant Code:

MyCloseable().use {
// do stuff with myCloseable
}

MyClosable().use { it.doStuff() }

functionThatReturnsClosable().use { it.doStuff() }

MultilineRawStringIndentation

This rule ensures that raw strings have a consistent indentation.

The content of a multi line raw string should have the same indentation as the enclosing expression plus the configured indentSize. The closing triple-quotes (""") must have the same indentation as the enclosing expression.

Warning: Rule MultilineRawStringIndentation overlaps with StringTemplateIndent from the ktlint rule set.

Active by default: No

Configuration options:

  • indentSize (default: 4)

    indentation size

  • trimmingMethods (default: ['trimIndent', 'trimMargin'])

    allows to provide a list of multiline string trimming methods

Noncompliant Code:

val a = """
Hello World!
How are you?
""".trimMargin()

val a = """
Hello World!
How are you?
""".trimMargin()

Compliant Code:

val a = """
Hello World!
How are you?
""".trimMargin()

val a = """
Hello World!
How are you?
""".trimMargin()

NestedScopeFunctions

Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it's easy to get confused about the current context object and the value of this or it.

Reference

Active by default: No

Requires Type Resolution

Configuration options:

  • allowedDepth (default: 1)

    The maximum allowed depth for nested scope functions.

  • functions (default: ['kotlin.apply', 'kotlin.run', 'kotlin.with', 'kotlin.let', 'kotlin.also'])

    Set of scope function names which add complexity. Function names have to be fully qualified. For example 'kotlin.apply'.

Noncompliant Code:

// Try to figure out, what changed, without knowing the details
first.apply {
second.apply {
b = a
c = b
}
}

Compliant Code:

// 'a' is a property of current class
// 'b' is a property of class 'first'
// 'c' is a property of class 'second'
first.b = this.a
second.c = first.b

RedundantHigherOrderMapUsage

Redundant maps add complexity to the code and accomplish nothing. They should be removed or replaced with the proper operator.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

fun foo(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.map { it }
}

fun bar(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.map {
doSomething(it)
it
}
}

fun baz(set: Set<Int>): List<Int> {
return set.map { it }
}

Compliant Code:

fun foo(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
}

fun bar(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.onEach {
doSomething(it)
}
}

fun baz(set: Set<Int>): List<Int> {
return set.toList()
}

ReplaceSafeCallChainWithRun

Chains of safe calls on non-nullable types are redundant and can be removed by enclosing the redundant safe calls in a run {} block. This improves code coverage and reduces cyclomatic complexity as redundant null checks are removed.

This rule only checks from the end of a chain and works backwards, so it won't recommend inserting run blocks in the middle of a safe call chain as that is likely to make the code more difficult to understand.

The rule will check for every opportunity to replace a safe call when it sits at the end of a chain, even if there's only one, as that will still improve code coverage and reduce cyclomatic complexity.

Active by default: No

Requires Type Resolution

Noncompliant Code:

val x = System.getenv()
?.getValue("HOME")
?.toLowerCase()
?.split("/") ?: emptyList()

Compliant Code:

val x = getenv()?.run {
getValue("HOME")
.toLowerCase()
.split("/")
} ?: emptyList()

TrimMultilineRawString

All the Raw strings that have more than one line should be followed by trimMargin() or trimIndent().

Active by default: No

Configuration options:

  • trimmingMethods (default: ['trimIndent', 'trimMargin'])

    allows to provide a list of multiline string trimming methods

Noncompliant Code:

"""
Hello World!
How are you?
"""

Compliant Code:

"""
| Hello World!
| How are you?
""".trimMargin()

"""
Hello World!
How are you?
""".trimIndent()

"""Hello World! How are you?"""

UnnecessaryAny

Turn on this rule to flag usage of any which can either be replaced with simple contains call or can removed entirely to reduce visual complexity.

Active by default: No

Requires Type Resolution

Noncompliant Code:

val a = 1
list.any { it == a }

Compliant Code:

val a = 1
list.contains(a)

UnnecessaryApply

apply expressions are used frequently, but sometimes their usage should be replaced with an ordinary method/extension function call to reduce visual complexity

Active by default: Yes - Since v1.16.0

Requires Type Resolution

Noncompliant Code:

config.apply { version = "1.2" } // can be replaced with `config.version = "1.2"`
config?.apply { environment = "test" } // can be replaced with `config?.environment = "test"`
config?.apply { println(version) } // `apply` can be replaced by `let`

Compliant Code:

config.apply {
version = "1.2"
environment = "test"
}

UnnecessaryFilter

Unnecessary filters add complexity to the code and accomplish nothing. They should be removed.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

val x = listOf(1, 2, 3)
.filter { it > 1 }
.count()

val x = listOf(1, 2, 3)
.filter { it > 1 }
.isEmpty()

Compliant Code:

val x = listOf(1, 2, 3)
.count { it > 2 }

val x = listOf(1, 2, 3)
.none { it > 1 }

UnnecessaryLet

let expressions are used extensively in our code for null-checking and chaining functions, but sometimes their usage should be replaced with an ordinary method/extension function call to reduce visual complexity.

Active by default: No

Requires Type Resolution

Noncompliant Code:

a.let { print(it) } // can be replaced with `print(a)`
a.let { it.plus(1) } // can be replaced with `a.plus(1)`
a?.let { it.plus(1) } // can be replaced with `a?.plus(1)`
a?.let { that -> that.plus(1) }?.let { it.plus(1) } // can be replaced with `a?.plus(1)?.plus(1)`
a.let { 1.plus(1) } // can be replaced with `1.plus(1)`
a?.let { 1.plus(1) } // can be replaced with `if (a != null) 1.plus(1)`

Compliant Code:

a?.let { print(it) }
a?.let { 1.plus(it) } ?.let { msg -> print(msg) }
a?.let { it.plus(it) }
val b = a?.let { 1.plus(1) }

UnnecessaryReversed

If a sort operation followed by a reverse operation or vise versa should be avoided, and both statements should be replaced by single equivalent sort operation.

Active by default: No

Requires Type Resolution

Noncompliant Code:

listOf(1,2)
.sorted()
.asReversed()

Compliant Code:

listOf(1,2)
.sortedDescending()

UseAnyOrNoneInsteadOfFind

Turn on this rule to flag find calls for null check that can be replaced with a any or none call.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

listOf(1, 2, 3).find { it == 4 } != null
listOf(1, 2, 3).find { it == 4 } == null

Compliant Code:

listOf(1, 2, 3).any { it == 4 }
listOf(1, 2, 3).none { it == 4 }

UseCheckNotNull

Turn on this rule to flag check calls for not-null check that can be replaced with a checkNotNull call.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

check(x != null)

Compliant Code:

checkNotNull(x)

UseCheckOrError

Kotlin provides a concise way to check invariants as well as pre- and post-conditions. Prefer them instead of manually throwing an IllegalStateException.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

if (value == null) throw IllegalStateException("value should not be null")
if (value < 0) throw IllegalStateException("value is $value but should be at least 0")
when(a) {
1 -> doSomething()
else -> throw IllegalStateException("Unexpected value")
}

Compliant Code:

checkNotNull(value) { "value should not be null" }
check(value >= 0) { "value is $value but should be at least 0" }
when(a) {
1 -> doSomething()
else -> error("Unexpected value")
}

UseEmptyCounterpart

Instantiation of an object's "empty" state should use the object's "empty" initializer for clarity purposes.

Active by default: Yes - Since v2.0.0

Requires Type Resolution

Noncompliant Code:

arrayOf()
listOf() // or listOfNotNull()
mapOf()
sequenceOf()
setOf()

Compliant Code:

emptyArray()
emptyList()
emptyMap()
emptySequence()
emptySet()

UseIfEmptyOrIfBlank

This rule detects isEmpty or isBlank calls to assign a default value. They can be replaced with ifEmpty or ifBlank calls.

Active by default: No

Requires Type Resolution

Noncompliant Code:

fun test(list: List<Int>, s: String) {
val a = if (list.isEmpty()) listOf(1) else list
val b = if (list.isNotEmpty()) list else listOf(2)
val c = if (s.isBlank()) "foo" else s
val d = if (s.isNotBlank()) s else "bar"
}

Compliant Code:

fun test(list: List<Int>, s: String) {
val a = list.ifEmpty { listOf(1) }
val b = list.ifEmpty { listOf(2) }
val c = s.ifBlank { "foo" }
val d = s.ifBlank { "bar" }
}

UseIsNullOrEmpty

This rule detects null or empty checks that can be replaced with isNullOrEmpty() call.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

fun foo(x: List<Int>?) {
if (x == null || x.isEmpty()) return
}
fun bar(x: List<Int>?) {
if (x == null || x.count() == 0) return
}
fun baz(x: List<Int>?) {
if (x == null || x.size == 0) return
}

Compliant Code:

if (x.isNullOrEmpty()) return

UseLet

if expressions that either check for not-null and return null in the false case or check for null and returns null in the truthy case are better represented as ?.let {} blocks.

Active by default: No

Noncompliant Code:

if (x != null) { transform(x) } else null
if (x == null) null else y

Compliant Code:

x?.let { transform(it) }
x?.let { y }

UseOrEmpty

This rule detects ?: emptyList() that can be replaced with orEmpty() call.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

fun test(x: List<Int>?, s: String?) {
val a = x ?: emptyList()
val b = s ?: ""
}

Compliant Code:

fun test(x: List<Int>?, s: String?) {
val a = x.orEmpty()
val b = s.orEmpty()
}

UseRequire

Kotlin provides a much more concise way to check preconditions than to manually throw an IllegalArgumentException.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

if (value == null) throw IllegalArgumentException("value should not be null")
if (value < 0) throw IllegalArgumentException("value is $value but should be at least 0")

Compliant Code:

requireNotNull(value) { "value should not be null" }
require(value >= 0) { "value is $value but should be at least 0" }

UseRequireNotNull

Turn on this rule to flag require calls for not-null check that can be replaced with a requireNotNull call.

Active by default: Yes - Since v1.21.0

Requires Type Resolution

Noncompliant Code:

require(x != null)

Compliant Code:

requireNotNull(x)

UseSumOfInsteadOfFlatMapSize

Turn on this rule to flag flatMap and size/count calls that can be replaced with a sumOf call.

Active by default: No

Requires Type Resolution

Noncompliant Code:

class Foo(val foo: List<Int>)
list.flatMap { it.foo }.size
list.flatMap { it.foo }.count()
list.flatMap { it.foo }.count { it > 2 }
listOf(listOf(1), listOf(2, 3)).flatten().size

Compliant Code:

list.sumOf { it.foo.size }
list.sumOf { it.foo.count() }
list.sumOf { it.foo.count { foo -> foo > 2 } }
listOf(listOf(1), listOf(2, 3)).sumOf { it.size }

UselessCallOnNotNull

The Kotlin stdlib provides some functions that are designed to operate on references that may be null. These functions can also be called on non-nullable references or on collections or sequences that are known to be empty - the calls are redundant in this case and can be removed or should be changed to a call that does not check whether the value is null or not.

Active by default: Yes - Since v1.2.0

Requires Type Resolution

Noncompliant Code:

val testList = listOf("string").orEmpty()
val testList2 = listOf("string").orEmpty().map { _ }
val testList3 = listOfNotNull("string")
val testString = ""?.isNullOrBlank()

Compliant Code:

val testList = listOf("string")
val testList2 = listOf("string").map { }
val testList3 = listOf("string")
val testString = ""?.isBlank()