Article

Suppressing annoying Kotlin compiler warnings

Use a Kotlin compiler plugin to automatically rid yourself of those warnings you don’t really care about

Sean Weiser

September 8, 2025

We recently found ourselves in a situation where some of the generated code in our networking library was referencing APIs we had just deprecated. This resulted in hundreds of similar lines in our logs at compile time:

This declaration overrides deprecated member but not marked as deprecated itself. Please add @Deprecated annotation or suppress. See https://youtrack.jetbrains.com/issue/KT-47902 for details
'createUser(User): Completable' is deprecated. Please use SuspendingUserOperations.createUser
This declaration overrides deprecated member but not marked as deprecated itself. Please add @Deprecated annotation or suppress. See https://youtrack.jetbrains.com/issue/KT-47902 for details
'getUser(String): Single<User>' is deprecated. Please use SuspendingUserOperations.getUser
This declaration overrides deprecated member but not marked as deprecated itself. Please add @Deprecated annotation or suppress. See https://youtrack.jetbrains.com/issue/KT-47902 for details
'getUsers(): Single<List<User>>' is deprecated. Please use SuspendingUserOperations.getUsers
This declaration overrides deprecated member but not marked as deprecated itself. Please add @Deprecated annotation or suppress. See https://youtrack.jetbrains.com/issue/KT-47902 for details
'updateUser(User): Completable' is deprecated. Please use SuspendingUserOperations.updateUser
...

Our logs became cluttered, causing us problems when trying to find important warnings or errors both locally and when running builds on our CI servers. There were a couple of solutions to this problem:

  1. In our generated code, add the @Suppress annotation where necessary. This is the solution we eventually went with, but it’s not nearly as interesting as option 2. It just involves hardcoding @Suppress("DEPRECATION") immediately before any function definition we detect using the Deprecated annotation.
  2. Create a Kotlin compiler plugin to automatically suppress any warnings from these generated files. This is more work initially, but has the potential to be more powerful and flexible than programmatically adding suppressions to our generated code.

So, how can we accomplish option 2?

Create a new project or module for the plugin

The first step is to create a spot for the plugin code. The code will not be referenced anywhere else, so it can be placed in a number of locations: either in an entirely separate project or a different module within your project. For the purposes of this example, I’ve created a new module within the project called custom-compiler-plugin.

The module’s build.gradle.kts should look like:

plugins {
kotlin("jvm")
id("com.google.devtools.ksp")
}

dependencies {
implementation(libs.kotlin.compiler.embeddable)
implementation("com.google.auto.service:auto-service-annotations:1.1.1")

ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
}

You’ll also need to reference it in the project’s settings.gradle.kts:

...
include(":custom-compiler-plugin")
...

Create a suppressor extension

From there, the most important step is actually specifying what logs are going to be suppressed.

In custom-compiler-plugin/src/main/java/com/example/compilerplugin/ExampleWarningsSuppressor.kt add the following:

package com.example.compilerplugin

import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor

class ExampleWarningsSuppressor : DiagnosticSuppressor {
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
return diagnostic.psiFile.name.contains("_Generated")
}
}

and modify it to your needs. In the above example, we’re suppressing all warnings from any file with a name that contains the substring _Generated (e.g. UserOperations_Generated.kt). This logic can be as simple or as complex as you want. You could just return true and suppress all warnings. Or you can make use of the PsiFile and PsiElement properties contained in the passed in Diagnostic object and make suppressions more fine-grained.

Register the suppressor extension

The next step is to hook into the build system and register your newly created suppressor extension.

In custom-compiler-plugin/src/main/java/com/example/compilerplugin/ExampleWarningsRegistrar.kt add:

package com.example.compilerplugin

import com.google.auto.service.AutoService
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor

@OptIn(ExperimentalCompilerApi::class)
@AutoService(ComponentRegistrar::class)
class ExampleWarningsRegistrar : ComponentRegistrar {
override fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
) {
Extensions
.getRootArea()
.getExtensionPoint(DiagnosticSuppressor.EP_NAME)
.registerExtension(ExampleWarningsSuppressor())
}
}

The only difference should be the name of the registrar and the object you pass into registerExtension(). If you want to expand the functionality of this registrar beyond just a diagnostic suppressor, there are other extension point names that can be used instead, but that goes beyond the scope of this article.

Note that this class is using the @AutoService annotation. Typically you’d need to create a resource file called META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar that points to your registrar class, but that is automatically handled for you when this particular annotation is applied.

Compile to a JAR and reference it in the application or library module

Whether you’ve added a new module to your project or created an entirely new project for the plugin, the final step will be to generate a JAR. From the command line, run:

./gradlew custom-compiler-plugin:assemble

Then copy the generated JAR from the build/libs directory to somewhere in your project. For this example I’ve moved it to a libs folder under the root directory.

In your application or library module’s build.gradle.kts add the following:

...
android {
...
kotlinOptions {
freeCompilerArgs = listOf(
...
"-Xplugin=$rootDir/libs/custom-compiler-plugin.jar"
)
}
}
...

Be sure to update the file name and path based on where you ended up placing the JAR.

Note that using a local JAR requires manually updating the freeCompilerArgs as described above. However, if you’re able to publish your binary, either locally or to a remote repository like Maven, there are alternatives to modifying the compiler argument list yourself. I’ll explore these in a future article, but a couple of well known libraries are able to handle this in their own way:

  • The Jetpack Compose compiler plugin defines its Gradle plugin as a KotlinCompilerPluginSupportPlugin . By doing so, the published JAR will automatically be included as a plugin in the compiler argument list.
  • Hilt does the same thing, but in a round-about way. Hilt uses KSP to run its annotation processors. Internally, KSP defines a Gradle plugin that also subclasses KotlinCompilerPluginSupportPlugin, which handles the compiler arguments just like Compose.

Compile your project

The final step is to compile your project and verify those warnings have been eliminated. If you need to make adjustments you’ll mostly likely need to update the isSuppressed function in the suppressor class. Each time you make changes you’ll also need to recompile the plugin JAR and copy it back into your project.

Once you confirm that everything is working, sit back and enjoy your clean, easy to read logs.



Sean works at Livefront , where he tries not to ignore too many warnings (at least the important ones).