The hidden Kotlin gem you didn't think you'll love: Deprecations with ReplaceWith
Tags: kotlin, intellij, tooling, replacewith
Published: 2020-05-29 09:49:04

This is one of the features in Kotlin that is enabled by IntelliJ IDEA and the Kotlin language working together like a charm, in order to give you a pleasant developer experience. If you look at the [replaceWith](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deprecated/replace-with.html) documentation, you'd probably move on and never give it a second look. Maybe you'll click through to the documentation of the [Deprecated](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deprecated/) annotation. At least the [ReplaceWith](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-replace-with/) docs say what this enables. But because there are no examples, you might just close your browser tab and code on.

But wait! There is so much to love about this. Trust me.

## Deprecation of subSequence in Kotlin's stdlib

Let's start with an example. In Kotlin 1.3.72, there still is an extension function `String.subSequence` that takes a `start` and an `end` index. But the Kotlin creators wanted it to be more explicit, so they added a new variant that takes `startIndex` and `endIndex` as parameters instead. So they deprecated the old one with the `@Deprecated` annotation - you can see how they did this [here](https://github.com/JetBrains/kotlin/blob/v1.3.72/libraries/stdlib/src/kotlin/text/Strings.kt#L346-L347). So if you use the old variant, you'll see it striked through like this:

![subSequence is striked through](https://dev-to-uploads.s3.amazonaws.com/i/8stdey9afe838spc8t3k.png)

But because they added a `replaceWith` parameter on that deprecation, IntelliJ will offer you to fix this automatically if you hit `Alt + Enter` on it - and you can also decide to automatically fix this *in your entire project*!

![IntelliJ offers to automatically replace the old variant](https://dev-to-uploads.s3.amazonaws.com/i/8owdp9k9bqah2f192pj3.png)

Live in action it looks like this:

![IntelliJ offers to automatically replace the old variant](https://dev-to-uploads.s3.amazonaws.com/i/eqo3ke30agfdu3xq2x0d.gif)

And all the person deprecating the old variant needed to do was to add this one liner before the old variant, worrying only about the code itself, and by doing this enabling millions of developers to be able to change their code in seconds:

```kotlin
@Deprecated("Use parameters named startIndex and endIndex.", ReplaceWith("subSequence(startIndex = start, endIndex = end)"))
```

Wouldn't it be nice if you could do the same? Yes, you can!

## Replace *all the things*

Let's use a simple example to start. Say, you have a codebase where someonce decided to put the word `do` in front of lots of methods, like `doLogout()`. But it's an API, so you can't just get rid of these - you'll have to guide developers to use the new variant `logout()` instead. This is how:

```kotlin
@Deprecated(
    message = "Use simpler logout() instead",
    replaceWith = ReplaceWith("logout()")
)
fun doLogout() {
    logout()
}
```

And automatically, `doLogout()` will become ~~`doLogout()`~~ for those who use it. And by pressing `Alt + Enter` and auto-applying the fix, developers now can update this to `logout()` in the entire project.

Ok, but what about this example? Here, we have the parameters in a peculiar order and a peculiar naming, and we want to be able to clean that up, without breaking the project for anyone:

```kotlin
fun doLogin(pwd: String, usr : String) {
    login(usr, pwd)
}
```

Well actually, you can use the names `pwd` and `usr` in your ReplaceWith string, and IntelliJ will automatically insert the expressions used for `pwd` and `usr` into the replaced code!

```kotlin
@Deprecated(
    message = "Use simpler login instead with correct parameter order",
    replaceWith = ReplaceWith("login(usr, pwd)")
)
fun doLogin(pwd: String, usr : String) {
    login(usr, pwd)
}

fun login(username : String, password: String) {
}
```

I didn't see the implementation, but if I would take a good guess this has to work by the IDE taking the old Abstract Syntax Tree (AST), building the new AST of the `ReplaceWith` string, and copying over the expressions to the right placeholder positions. If anyone finds where this logic is implemented, please send me the GitHub link - I'd love to include it in this blog post! 🌟

Because it's so nice, here's how this looks if you apply it in the IDE:

![doLogin being automatically replaced by IntelliJ IDEA](https://dev-to-uploads.s3.amazonaws.com/i/zmajya9iw3r890wq4g05.gif)

## It can do even more: add more code, and imports

Let's say we have the `doLogin` method from before, but instead of using two parameters, we want to insert a new `Credentials` data class that sits in the `auth` package:

```kotlin
package auth

data class Credentials(
    val username: String,
    val password: String
)
```

This is how we do it:

```kotlin
@Deprecated(
    message = "Use simpler login with Credentials",
    replaceWith = ReplaceWith(
        expression = "login(Credentials(usr, pwd))",
        imports = ["auth.Credentials"]
    )
)
fun doLogin(pwd: String, usr: String) {
}
```

When a developer applies the auto-fix with `Alt + Enter`, this will automatically add an import as well as fixing the code in-place! This time, let's fix multiple occurrences at the same time, because this looks so cool. Imagine having hundreds of these in your code base, in dozens of files - all fixed automatically! 🎉

![Replacing multiple occurrences at once, while adding an import](https://dev-to-uploads.s3.amazonaws.com/i/zurib07x8d6m9vyk3tzy.gif)

## Deprecated can do more

That's it for now about `ReplaceWith`! One more thing about `@Deprecated` annotation: you can add a [DeprecationLevel](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deprecation-level/) to it as well. The default is `WARNING`. But if you want to up the game and you want all developers to *finally* move over to the new API, you can force them by making this an `ERROR`. These have to be fixed first, otherwise their code won't compile. And finally, once all developers have moved over, you can even set this to `HIDDEN` - so no one will even see the old method anymore. This is useful if you still want to be binary-compatible. Here is how you can use all these:

```kotlin
@Deprecated(message = "", level = DeprecationLevel.WARNING)
fun warning() {
}

@Deprecated(message = "", level = DeprecationLevel.ERROR)
fun error() {
}

@Deprecated(message = "", level = DeprecationLevel.HIDDEN)
fun hidden() {
}
```

And here is how they will look when used:

![Effect of all deprecation levels as shown in the IDE](https://dev-to-uploads.s3.amazonaws.com/i/w7n76h66dpj8lj14gn2b.png)

I hope this will be a nice addition to your Kotlin toolbox!

## Recap

So to recap, using `@Deprecated` with `ReplaceWith` lets you:

- deprecate old code, and automatically suggest the desired change
- this desired change will be available to all developers working on that code, and they can apply your code change with `Alt + Enter` instantly
- it can even re-order the old parameter values if you need them to, or let you add entire new code constructs and imports
- add deprecation levels to decide how fast users of the old code need to fix this
- be more productive and have more fun!

Edit: [Russell Wolf](https://twitter.com/RussHWolf) added a comment on Twitter that you can use this feature also for discovering APIs, which I think is really cool:

{% twitter 1266384210589372416 %}

Have an awesome Kotlin! 🎉

If you liked this post, please give it a ❤️, click the `+FOLLOW` button below and follow me on [Twitter](https://twitter.com/mreichelt)! This will help me stay motivated to create many more of these posts! 😊

*Cover photo by [Malcolm Lightbody](https://unsplash.com/@mlightbody) on [Unsplash](https://unsplash.com/photos/mfKdQMDSUSU).*