---
title: Keep Rules in the Age of Kotlin | Guardsquare
description: Learn why you need to think in terms of Java when writing keep rules for Kotlin code and how to maintain security in the Age of Kotlin Programming Language.
image: https://23593082.hs-sites.com/hubfs/Imported_Blog_Media/Keep-Rules-in-the-Age-of-Kotlin-2.jpg
---

[![guardsquare-logo](https://insights.guardsquare.com/hs-fs/hubfs/Logos/logo-white.png?length=536&name=logo-white.png "guardsquare-logo") ](https://23593082.hs-sites.com/)

- pricing

[Request Pricing](https://www.guardsquare.com/request-pricing?hsCtaTracking=646b0379-d596-4b1a-8526-2ccbdbb0f81b%7Ce8809087-a83f-4476-8b22-34b9572013fd)

[Login](https://customers.guardsquare.com/login?utm_referrer=https%3A%2F%2Fwww.guardsquare.com%2F)

En

- [中文](https://www.guardsquare.com/zh-hans)
- [한국어](https://www.guardsquare.com/ko)
- [Português](https://www.guardsquare.com/pt-br)
- [Español](https://www.guardsquare.com/es)

<https://twitter.com/intent/tweet?original_referer=https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin&url=https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin&source=tweetbutton> <http://www.linkedin.com/shareArticle?mini=true&url=https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin> <http://www.facebook.com/share.php?u=https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin> [mailto:?subject=Check%20out%20https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin](mailto:?subject=Check%20out%20https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin)

April 12, 2022

# Keep Rules in the Age of Kotlin

Written by: James Hamilton - Software Engineer

![](https://23593082.hs-sites.com/hubfs/Imported_Blog_Media/Keep-Rules-in-the-Age-of-Kotlin-2.jpg)

Kotlin is relatively young – only 11 years old – and Android has only had [official support for Kotlin for five years](https://android-developers.googleblog.com/2017/05/android-announces-support-for-kotlin.html). Yet Kotlin is now the go-to language for Android development, and is becoming more popular for desktop and server application development. Kotlin is used by over [60% of Android professional Android developers](https://developer.android.com/kotlin) and it's estimated that 1 million professional [software developers use Kotlin as 1 of their 3 primary languages](https://blog.jetbrains.com/kotlin/2021/09/the-actual-number-of-kotlin-developers-or-who-our-active-users-are/#:~:text=Based%20on%20the%20results%20and,of%20their%203%20primary%20languages.).

Clearly, we are truly in a new age: the Age of Kotlin.

## ProGuard in the Age of Kotlin

[ProGuard](https://23593082.hs-sites.com/proguard), which turns 20 this year, was originally created in the era of Java 1.4, Java Applets, J2ME and Sun Microsystems. Though Kotlin was designed to be interoperable with Java from the start and designed to run on the JVM, much has changed in 20 years. Kotlin is a much more modern language which has many features that Java does not.

In my presentation at Droidcon London, [Keep Rules in the Age of Kotlin](https://23593082.hs-sites.com/webinar/keep-rules-in-the-age-of-kotlin), I laid out some potential security concerns, such as leaking data via Kotlin assertions, and the potential impact of [Kotlin metadata](https://github.com/Guardsquare/kotlin-metadata-printer) on the size of apps. I recommend you watch the full presentation for an overview of the potential hidden pitfalls in the Age of Kotlin as well as some ideas for mitigations.

In this post, we’ll dive deeper into one section of the Droidcon presentation: the complications of writing keep rules for Kotlin code. You’ll learn why you need to think in terms of Java when writing keep rules for Kotlin code, using examples from the [Droidcon London 2021: Keep Rules in the Age of Kotlin ProGuard Playground](https://playground.proguard.com/p/0ptwmA); you’ll also find the answers to the playground challenges there.

## What is a keep rule anyway and why do we need them?

ProGuard, and similar tools like R8 and Redex, aim to shrink and optimize code as much as possible. One shrinking and obfuscation technique that can cause issues is the renaming of identifiers, such as class names, methods or fields, to shorter names (which obfuscates names while also making apps smaller). This can lead to problems when using reflection to dynamically access classes or members at runtime.

Consider the following Java class that contains a field named`myField`and uses reflection to access the field by name:

```
import java.lang.reflect.*;

public class Reflection {
    private String myField = "Hello World";

    public static void main(String[] args) throws Exception {
        Reflection obj = new Reflection();
        Field field = obj.getClass().getDeclaredField("myField");
        System.out.println(field.get(obj)); // prints Hello World
    }
}
```

If a shrinker, like ProGuard, renames the field to some shorter name (`a`, for example) then `getDeclaredField(“myField”)`will no longer work.

Though this case is simple, the field name could be generated dynamically at runtime. For example, libraries such as [jackson](https://github.com/FasterXML/jackson) rely on reflection to map JSON to model classes, so we need a way to tell ProGuard “don’t rename or remove this field”. In other words, we need to tell ProGuard to keep the field!

So, if we have a field`myField`in a class named`Reflection`that we want to tell ProGuard to keep, we can express this with the following keep rule:

```
-keep class Reflection {
    private java.lang.String myField;
}
```

ProGuard introduced keep rules 20 years ago, and they are now the de facto standard in the Android world; in fact, they are also used by R8, Redex and [DexGuard](https://23593082.hs-sites.com/dexguard). ProGuard keep rules were originally designed for, and inspired by, the Java class file format which explains why the rules look suspiciously like Java signatures (and why they don’t look like Kotlin signatures!).

ProGuard keep rules also support various wildcards, and alongside other configuration rules make them a very powerful tool for configuring ProGuard. A good place to get started is the [ProGuard manual](https://23593082.hs-sites.com/manual/quickstart), along with the [ProGuard Playground](https://playground.proguard.com/), which helps visualize the classes, methods and fields in your app that are kept or not by keep rules.

## But Kotlin Compiles to Java bytecode, right?

Kotlin compiles to Java bytecode and is interoperable with Java, so there shouldn’t be any problem writing keep rules … in theory. But Kotlin is not Java – what you write in Kotlin code and what comes out of the Kotlin compiler can seem disconnected. And you need to write keep rules for the latter, not the former, since shrinkers operate on the Java bytecode!

### Top-level declarations: Keeping the Kotlin`main`function

In Java, all declarations must be within a class, even a simple “Hello World” application needs a class declaration. Kotlin, in contrast, supports top-level declarations. For example, in the following Kotlin file,`Main.kt`, a top-level constant, a property and main function are defined:

```
const val MESSAGE = "Hello World"

val message: String
    get() = MESSAGE

fun main(): Unit {
    println(message)
}
```

If these declarations are not in a class, how can we write a keep rule for such top-level declarations given that all keep rules need a class name, or at least a wildcard matching a class?

Of course, since Kotlin compiles to Java, there is an actual class introduced behind the scenes! Such classes (known as file facades) take on the name of the Kotlin file with the suffix`Kt`. In this case, a class file is generated named`MainKt.class`.

So, we now know how to write a keep rule to keep the file facade class:

```
-keep class MainKt
```

But what about the main function? Surely it’ll be simple?

If you compare the keep rule below, which seems reasonable when thinking in terms of Kotlin code, with the actual methods in the`MainKt`class, you’ll notice that the keep rule doesn’t match either`main`method generated by the Kotlin compiler.

```
# Incorrect keep rule
-keep class MainKt {
    kotlin.Unit main();
}
```

```
$ javap MainKt.class 
Compiled from "Main.kt"
public final class MainKt {
  public static final void main();
  public static void main(java.lang.String[]);
}
```

 Remember, *we need to write keep rules in terms of the compiled Java class files, not Kotlin code*. An additional reminder: the main entry point for the JVM has the signature `public static void main(java.lang.String[])`.

The Kotlin compiler generates a synthetic `main`method that has the correct signature, which delegates to the second `main`method without parameters (also notice that the Kotlin `kotlin.Unit`type becomes the Java `void`type). Therefore, a keep rule that keeps the main Kotlin function would be:

```
-keep class MainKt {
    public static void main(java.lang.String[]);
    # or using a wildcard for parameters
    # public static void main(...);
}
```

| ### Challenge |
| --- |
| The constant property`MESSAGE`and the property getter for the`MESSAGE`property are also compiled to members in the file facade`MainKt`class. How would you write keep rules for them? Give it a go in the playground below, using the entity tree to get an idea of how the members are compiled from Kotlin code to Java classes.     HINT: Constant properties are compiled to static final fields and the property getter is compiled to a method. |

### Kotlin Types

We’ve already seen how a return type of`kotlin.Unit`compiles to a Java`void`return type, but what about when a function has a parameter of type`kotlin.Unit`? And how about other Kotlin specific types? In the following snippet, we have`Unit`,`Any`,`Nothing`and some [reified type parameters](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters).

```
package types

// Unit type as parameter type and return type
fun process(unit: Unit): Unit { }

// Any type
fun convertToString(any: Any): String = any.toString()

// Nothing type
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

// Reified types
open class MyFoo
inline fun <reified T> foo(foo: T) = foo
inline fun <reified T : MyFoo> foo(foo: T) = foo
```

#### Unit

In the example above, the`process`function has a return type of`kotlin.Unit`and the parameter is also of type`kotlin.Unit`. The return type will compile to void, but the parameter will remain a`kotlin.Unit`parameter! Taking this in account, a keep rule for this function would look like:

```
-keep class types.TypesKt {
    void process(kotlin.Unit);
}
```

#### Any

The`kotlin.Any`class is the root of the Kotlin class hierarchy, just like`java.lang.Object`is the root of the Java class hierarchy. The`kotlin.Any`type is compiled to the`java.lang.Object`type, so the keep rule for the`convertToString(Any): String`function is simply:

```
-keep class types.TypesKt {
    java.lang.String convertToString(java.lang.Object);
}
```

#### Nothing

Kotlin also has a type`Nothing`that represents a value that never exists. This is useful, for example, to tell the compiler that a function never returns because it always throws an exception, like the`fail`function in the example above.

So if it never returns, the compiled Java return type would be`void`, right? Well … almost! Under the hood, the Java method actually *returns the wrapper type`java.lang.Void`*! So a keep rule would look something like:

```
-keep class types.TypesKt {
    java.lang.Void fail(java.lang.String);
}
```

| ### Challenge |
| --- |
| How would you write keep rules for the`foo`functions that have reified type parameters in the example? Try writing a keep rule for the`foo`functions in the playground below, using the entity tree to discover what the compiled`types.TypesKt`class looks like. HINT: The Kotlin compiler may or may not be able to determine the concrete type, depending on how much information it has! |

### Companion objects

Kotlin [companion objects](https://kotlinlang.org/docs/object-declarations.html#companion-objects) are singleton objects tied to a class but not a specific instance of a class. In this way, they provide a similar feature (though not the same) as Java static methods. Companion objects can be either unnamed or named, and they can contain property and function declarations.

```
package companion

class Foo {
    // Unnamed companion
    companion object {
        const val CONSTANT = "bar"
        fun foo() = println("foo")
    }
}

class Bar {
    // Named companion
    companion object MyCompanion {
        fun bar() = println("bar")
    }
}
```

It begs the question: if a companion object is not a class and it has no name, how can we write a keep rule for it?

As before, we must think in terms of Java class files and not Kotlin code! The important thing to know about companion objects is that *they are compiled to Java inner classes using the name`Companion`*or their specified name (`MyCompanion`in the`Bar`example above).

As a reminder, the naming convention for compiled inner classes in Java combines both the outer class and inner class names using the dollar symbol as a separator (e.g.`OuterClass$InnerClass`).

So the companion class of`companion.Foo`will be compiled to a class named`companion.Foo$Companion`. This means that a keep rule to keep`companion.Foo`’s companion would look like this:

```
-keep class companion.Foo$Companion
# or for named companions:
-keep class companion.Bar$MyCompanion
```

| ### Challenge |
| --- |
| How would you write a keep rule for the property`CONSTANT`or the function`foo`? Try to write a keep rule in the playground below, using the entity tree to get an idea of how`CONSTANT`and`foo`are compiled to Java entities. HINT: Although companion functions are compiled to Java methods in the companion object class, constant properties are compiled to fields in the outer class! |

### Object declarations

We’ve already seen one type of object – the companion – but object declarations allow the general declaration of singleton classes. For example, the`Logger`class in the example below declares a singleton logger and looks similar to how you would create such a class in Java containing static methods for different log levels.

```
package logger

// Object declaration
object Logger {
    fun verbose(message: String) = println(message)
    fun info(message: String) = println(message)
}

fun useLogger() {
    Logger.verbose("foo")
}
```

As usual, if you want to keep the`Logger`methods, you need to know what it looks like when compiled to a Java class file.

First, the easy part: an object declaration is compiled to a class of the same name.

Secondly, the functions: they look like they are called like Java static methods (since you call them like`Logger.verbose(“foo”)`). This would imply they are compiled to Java static methods. A keep rule would then look something like this:

```
-keep class logger.Logger {
    public static void verbose(java.lang.String);
    public static void info(java.lang.String);
}
```

But, this keep rule won’t work! An important thing to know for object declarations is that the methods are *instance methods.*

Under the hood, the Java class also contains a field`INSTANCE`which holds a singleton instance; all calls to these methods, even though they look like Java static method calls at first glance, are calls to these instance methods on the object in the`INSTANCE`field. This means that the keep rules should *not include the static modifier*:

```
-keep class logger.Logger {
    public void verbose(java.lang.String);
    public void info(java.lang.String);
}
```

| ### Challenge |
| --- |
| Did you know that [ProGuard can be used to remove logging calls](https://community.guardsquare.com/t/how-to-remove-debug-logging-with-proguard/360) from your release app? Given what you’ve learned so far about writing keep rules, how would you remove the logging calls to the`Logger`singleton using`-assumenosideeffects`? HINT: Other ProGuard configuration rules, like`-assumenosideeffects`have the same format as the keep rules! |

#### Extension functions & properties

Kotlin, unlike Java, allows adding new functionality to existing classes without using inheritance. This is achieved via the use of extension functions and properties – they allow extending any class, including classes from the Kotlin runtime library and third-party libraries. The following example shows extensions on the`String`class:

```
// File: extension/Ext.kt
package extension

fun String.foo(): String = "foo"
// println(“Hello World”.foo()) prints foo

var String.foo: String
    get() = "foo"
    set(value) { /* do something with value */ }
// println(“Hello World”.foo)  prints foo
```

Given that functions or properties can be added to classes that you don’t even have the source code for, how do they get compiled? And how do we write keep rules for them? Like this?

```
-keep class java.lang.String {
    java.lang.String foo();
}
```

Of course,`java.lang.String`is a Java library class, and a shrinker won’t (and can’t!) touch it, so writing such a keep rule doesn’t make sense (ProGuard will actually warn you about keep rules for library classes). The important thing to know about extensions is that though they are compiled to Java methods like any other Kotlin function, they have an extra first parameter for the receiver object (which is an instance of the class or property that is being extended).

For example, the String extension function`foo`in the example is compiled to a Java method with the signature`java.lang.String foo(java.lang.String)`. Using this knowledge, combined with what we’ve already learned about top-level declarations (and given that the function is in the file`extension/Ext.kt`), we can craft a keep rule to keep this extension function:

```
-keep class extension.ExtKt {
    public java.lang.String foo(java.lang.String);
}
```

| ### Challenge |
| --- |
| How would you write keep rules for the getter & setters of the`foo`extension property on the`String`class? Try to write a keep rule in the playground below, using the entity tree to get an idea of how extension properties are compiled to Java entities. HINT: The getter has the receiver as its only parameter but the setter will have two parameters: the receiver and the value. |

### JVM annotations

Kotlin was designed with Java interoperability in mind, so it’s natural to call Kotlin code from Java. But there are some [differences between Kotlin and Java that require some attention](https://kotlinlang.org/docs/java-to-kotlin-interop.html) so Kotlin provides a way to tweak the generation of Java code using annotations.

In the following example, three annotations are used:

- [@JvmName](https://kotlinlang.org/docs/java-to-kotlin-interop.html#handling-signature-clashes-with-jvmname) to change the name of a property
- [@JvmOverloads](https://kotlinlang.org/docs/java-to-kotlin-interop.html#overloads-generation) to generate multiple Java methods instead of a single method with all parameters, since Java doesn’t have the concept of default parameters
- [@JvmStatic](https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-methods) to tell the compiler to create a static method in the outer class that delegates to the companion function

```
package jvm

class MyClass {
    @JvmName("foobar") fun foo() = "bar"

    @JvmOverloads
    fun overloaded(a: Int = 1, b: String = "foo") { }

    companion object {
        @JvmStatic fun jvmstatic() = "bar"
    }
}
```

Given that`@JvmName`is used to change the name a keep rule is straightforward, simply use the name specified in the annotation:

```
-keep class jvm.MyClass {
    public java.lang.String foobar();
}
```

For`@JvmOverloads`, presumably, when keeping this function all overloaded versions should be kept. This is easy to do with the`...`wildcard, which means “any number and any type of parameters”:

```
-keep class jvm.MyClass {
    public void overloaded(...);
}
```

For`@JvmStatic`, a static method is created in the outer`jvm.MyClass`class that delegates to the companion method. A keep rule for this is simply:

```
-keep class jvm.MyClass {
    public static java.lang.String jvmstatic();
}
```

| ### Challenge |
| --- |
| The three annotations shown here aren’t the only [JVM platform annotations](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/#annotations). How do other annotations, such as @JvmField, @JvmMultifileClass, @JvmWildcard etc, affect the generated code and therefore how you write keep rules? HINT: Create some samples using these annotations and upload them to the [ProGuard Playground](https://playground.proguard.com/): you can then see what the generated entities look like in the entity tree and interactively write keep rules for them. |

## Conclusion

We’ve seen how, in the Age of Kotlin, writing keep rules is not always easy or intuitive. It requires some understanding of how Kotlin code is compiled to Java class files but the task is made a lot easier with the help of the [ProGuard Playground](https://playground.proguard.com/).

Every example in this post, including solutions to the challenges with embedded playgrounds, can be found in the [Droidcon London 2021: Keep Rules in the Age of Kotlin Playground](https://playground.proguard.com/p/0ptwmA) – have a look and play around with the keep rules yourself

*Remember: you need to write keep rules in terms of the compiled Java class files, not Kotlin code!*

We also mentioned that writing keep rules is not the only complication brought about by Kotlin – to find out more, watch the full Droidcon London Keep Rules in the Age of Kotlin presentation, and keep an eye out for our upcoming deep dive into the security implications of Kotlin assertions.

![webinar-keep_rules_in_the_age_of_kotlin_](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

Tag(s): [Android](https://23593082.hs-sites.com/amilia-test/tag/android) , [Technical](https://23593082.hs-sites.com/amilia-test/tag/technical) , [ProGuard & R8](https://23593082.hs-sites.com/amilia-test/tag/proguard-r8) , [DexGuard](https://23593082.hs-sites.com/amilia-test/tag/dexguard)

## [James Hamilton - Software Engineer](https://23593082.hs-sites.com/amilia-test/author/james-hamilton-software-engineer)

Connect with the author

## Discover how Guardsquare provides industry-leading protection for mobile apps.

## Other posts you might be interested in

[

** 11 min read  | August 22, 2023

## Webinar Recap: Accessibility Features and Overlays on Android

Android Protection Security Research 

](https://23593082.hs-sites.com/amilia-test/blog/app-permissions-abuse) [

** 7 min read  | August 15, 2023

## What is RASP and Why It Matters to Mobile App Developers

Android iOS Protection DexGuard iXGuard 

](https://23593082.hs-sites.com/amilia-test/blog/rasp-dynamic-analysis)

## Subscribe to our newsletter

Stay in the know with monthly updates right to your inbox.

![HubSpot CMS](https://cdn2.hubspot.net/hubfs/302335/hubspot-cms-logo-gray.svg "HubSpot CMS")

[General Terms](https://23593082.hs-sites.com/terms-of-use) | [Privacy Policy](https://23593082.hs-sites.com/privacy-policy) | [Cookie Policy](https://23593082.hs-sites.com/cookie-policy)

Tervuursevest 362 bus 1, 3000 Leuven, Belgium | VAT: BE0550675829 |

© 2016-2026 Guardsquare nv. All rights reserved.

<https://www.facebook.com/guardsquare> <https://twitter.com/Guardsquare> <https://www.linkedin.com/company/guardsquare/> <https://www.youtube.com/channel/UCP9s5F1ksT1E-7vSF2CDyhw> <https://github.com/Guardsquare>

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "James Hamilton - Software Engineer",
    "url" : "https://23593082.hs-sites.com/amilia-test/author/james-hamilton-software-engineer"
  },
  "dateModified" : "2023-09-19T17:55:03.215Z",
  "datePublished" : "2022-04-12T04:00:00.000Z",
  "headline" : "Keep Rules in the Age of Kotlin | Guardsquare",
  "image" : [ "https://23593082.hs-sites.com/hubfs/Imported_Blog_Media/Keep-Rules-in-the-Age-of-Kotlin-2.jpg" ],
  "mainEntityOfPage" : {
    "@id" : "https://23593082.hs-sites.com/amilia-test/blog/keep-rules-in-the-age-of-kotlin",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject"
    }
  }
}
```