1. Introduction

Kotlin introduces the concept of Extension Functions – which are a handy way of extending existing classes with new functionality without using inheritance or any forms of the Decorator pattern – after defining an extension. we can essentially use it – as it was part of the original API.

This can be very useful in making our code easy to read and maintain, as we’re able to add methods that are specific to our needs and have them appear to be part of the original code, even when we don’t have access to the sources.

For example, we might need to perform XML escaping on a String. In standard Java code, we’d need to write a method that can perform this and call it:

String escaped = escapeStringForXml(input);

Whereas written in Kotlin, the snippet could be replaced with:

val escaped = input.escapeForXml()

Not only is this easier to read, but IDEs will be able to offer the method as an autocomplete option the same as if it was a standard method on the String class.

2. Standard Library Extension Functions

The Kotlin Standard Library comes with some extension functions out-of-the-box.

2.1. Context Adjusting Extension Functions

Some generic extensions exist and can be applied to all types in our application. These could be used to ensure that code is run in an appropriate context, and in some cases to ensure that a variable isn’t null.

It turns out that, most likely, we’re leveraging extensions without realizing this.

One of the most popular ones is possibly the let() function, which can be called on any type in Kotlin – let’s pass a function to it that will be executed on the initial value:

val name = "Baeldung"
val uppercase = name
  .let { n -> n.toUpperCase() }

It’s similar to the map() method from Optional or Stream classes – in this case, we pass a function representing an action that converts a given String into its upper-cased representation.

The variable name is known as the receiver of the call because it’s the variable that the extension function is acting upon.

This works great with the safe-call operator:

val name = maybeGetName()
val uppercase = name?.let { n -> n.toUpperCase() }

In this case, the block passed to let() is only evaluated if the variable name was non-null. This means that inside the block, the value n is guaranteed to be non-null. More on this here.

There are other alternatives to let() that can be useful as well though, depending on our needs.

The run() extension works the same as let(), but a receiver is provided as this value inside the called block:

val name = "Baeldung"
val uppercase = name.run { toUpperCase() }

apply() works the same as run(), but it returns a receiver instead of returning the value from the provided block.

Let’s take advantage of apply() to chain related calls:

val languages = mutableListOf<String>()
languages.apply { 
    add("Java")
    add("Kotlin")
    add("Groovy")
    add("Python")
}.apply {
    remove("Python")
}

Notice how our code becomes more concise and expressive not having to explicitly use this or it.

The also() extension works just like let(), but it returns the receiver in the same way that apply() does:

val languages = mutableListOf<String>()
languages.also { list -> 
    list.add("Java")
    list.add("Kotlin") 
    list.add("Groovy") 
}

The takeIf() extension is provided with a predicate acting on the receiver, and if this predicate returns true then it returns the receiver or null otherwise – this works similarly to a combination of a common map() and filter() methods:

val language = getLanguageUsed()
val coolLanguage = language.takeIf { l -> l == "Kotlin" }

The takeUnless() extension is the same as takeIf() but with the reversed predicate logic.

val language = getLanguageUsed()
val oldLanguage = language.takeUnless { l -> l == "Kotlin" }

2.2. Extension Functions for Collections

Kotlin adds a large number of extension functions to the standard Java Collections which can make our code easier to work with.

These methods are located inside _Collections.kt, _Ranges.kt, and _Sequences.kt, as well as _Arrays.kt for equivalent methods to apply to Arrays instead. (Remember that, in Kotlin, Arrays can be treated the same as Collections)

They’re far too many of these extension methods to discuss here, so have a browse of these files to see what’s available.

In addition to Collections, Kotlin adds a significant number of extension functions to the String class – defined in _Strings.kt. These allow us to treat Strings as if they were collections of characters.

All of these extension methods work together to allow us to write significantly cleaner, easier to maintain code regardless of the kind of collection we’re working with.

3. Writing Our Extension Functions

So, what if we need to extend a class with new functionality – either from the Java or Kotlin Standard Library or from a dependent library that we’re using?

Extension functions are written like any other function, but the receiver class is provided as part of the function name, separated with the period.

For example:

fun String.escapeForXml() : String {
    ....
}

This will define a new function called escapeForXml as an extension to the String class, allowing us to call it as described above.

Inside this function, we can access the receiver using this, the same as if we had written this inside the String class itself:

fun String.escapeForXml() : String {
  return this
    .replace("&", "&amp;")
    .replace("<", "&lt;")
    .replace(">", "&gt;")
}

3.1. Writing Generic Extension Functions

What if we want to write an extension function that is meant to be applied to multiple types, generically? We could just extend the Any type, – which is the equivalent of the Object class in Java – but there is a better way.

Extension functions can be applied to a generic receiver as well as a concrete one:

fun <T> T.concatAsString(b: T) : String {
    return this.toString() + b.toString()
}

This could be applied to any type that meets the generic requirements, and inside the function this value is typesafe.

For example, using the above example:

5.concatAsString(10) // compiles
"5".concatAsString("10") // compiles
5.concatAsString("10") // doesn't compile

3.2. Writing Infix Extension Functions

Infix methods are useful for writing DSL-style code, as they allow for methods to be called without the period or brackets:

infix fun Number.toPowerOf(exponent: Number): Double {
    return Math.pow(this.toDouble(), exponent.toDouble())
}

We can now call this the same as any other infix method:

3 toPowerOf 2 // 9
9 toPowerOf 0.5 // 3

3.3. Writing Operator Extension Functions

We could also write an operator function as an extension.

Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name – e.g., the plus operator method might be called using the + operator:

operator fun List<Int>.times(by: Int): List<Int> {
    return this.map { it * by }
}

Again, this works the same as any other operator method:

listOf(1, 2, 3) * 4 // [4, 8, 12]

4. Calling Kotlin Extension Function from Java

Let’s now see how Java operates with Kotlin extension functions.

In general, every extension function we define in Kotlin is available for us to use in Java. We should remember, though, that the infix method still needs to be called with dot and parentheses. Same with operator extensions — we can’t use only the plus character (+). These facilities are only available in Kotlin.

However, we can’t call some of the standard Kotlin library methods in Java, like let or apply, because they’re marked with @InlineOnly.

4.1. Visibility of the Custom Extension Function in Java

Let’s use one of the previously defined extension functions — String.escapeXml(). Our file containing the extension method is called StringUtil.kt.

Now, when we need to call an extension method from Java, we need to use a class name StringUtilKt. Note that we have to add the Kt suffix:

String xml = "<a>hi</a>";

String escapedXml = StringUtilKt.escapeForXml(xml);

assertEquals("&lt;a&gt;hi&lt;/a&gt;", escapedXml);

Please pay attention to the first escapeForXml parameter. This additional argument is an extension function receiver type. Kotlin with top-level extension function is a pure Java class with a static method. That’s why it needs to somehow pass the original String.

And of course, just like in Java, we can use static import:

import static com.baeldung.kotlin.StringUtilKt.*;

4.2. Calling a Built-in Kotlin Extension Function

Kotlin helps us write code easier and faster by providing many built-in extension functions. For example, there’s the String.capitalize() method, which can be called directly from Java:

String name = "john";

String capitalizedName = StringsKt.capitalize(name);

assertEquals("John", capitalizedName);

However, we can’t call extension methods marked with @InlineOnly from Java, for example:

inline fun <T, R> T.let(block: (T) -> R): R

4.3. Renaming the Generated Java Static Class

We already know that a Kotlin extension function is a static Java method. Let’s rename a generated Java class with an annotation @file:JvmName(name: String).

This has to be added at the top of the file:

@file:JvmName("Strings")
package com.baeldung.kotlin

fun String.escapeForXml() : String {
    return this
      .replace("&", "&amp;")
      .replace("<", "&lt;")
      .replace(">", "&gt;")
}

Now, when we want to call an extension method, we simply need to add the Strings class name:

Strings.escapeForXml(xml);

Also, we can still add a static import:

import static com.baeldung.kotlin.Strings.*;

5. Summary

Extension Functions are useful tools to extend types that already exist in the system – either because they don’t have the functionality we need or simply to make some specific area of code easier to manage.

We’ve seen here some extension functionsthat are ready to use in the system. Additionally, we explored various possibilities of extension functions.

Some examples of this functionality can be found over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.