Kotlin Library Friends - Using the Internals
Posted on 2025-01-12

Let’s say there is a library out there that has a very useful internal method inside of it.

package org.secret

internal class SecretSauce {
    fun onlyForFriends(): Int = 0
}

If you try to use it

import org.secret.SecretSauce
fun myUser(): Boolean {
    SecretSauce().onlyForFriends()
    return true
}

Kotlin compilation will fail with

> Task :lib:compileKotlin FAILED
e: file:///.../Library.kt:5:19 Cannot access 'class SecretSauce : Any': it is internal in file.
e: file:///.../Library.kt:8:5 Cannot access 'class SecretSauce : Any': it is internal in file.
e: file:///.../Library.kt:8:19 Cannot access 'class SecretSauce : Any': it is internal in file.

That’s good! Library internal bits should not be accessible to you.

However, if we really want we can bypass these errors with

@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

Jesse Wilson has a great write-up about this suppression.

Sadly, this workaround is getting phased out starting with Kotlin 2.1, and now you will get a warning that will eventually be an insuppressible error.

Is there a different way?

One might wonder, how do src/main and src/test compilations work, as they are two different Kotlin invocations in Gradle and src/test is able to access src/main internal types. If you look at the compiler argument options you’ll find -Xfriend-paths=path/to/classes/ which is the mechanism for this. If we were naughty, we could use this same argument for ourselves with an external library.

// Create a configuration we can use to track friend libraries
val friends = configurations.create("friends") {
    isCanBeResolved = true
    isCanBeConsumed = false
    isTransitive = false
}
// Make sure friends libraries are on the classpath
configurations.findByName("implementation")?.extendsFrom(friends)
// Make these libraries friends :) 
tasks.withType<KotlinCompile>().configureEach {
    friendPaths.from(friends.incoming.artifactView { }.files)
}
// Add libraries you want to 
dependencies {
    friends("org.example:secret-sauce:1.0.0")
}

Now, we no longer need the suppression, all the internal types will be accessible as if they were part of our own library. Sadly, it does seem that Intellij / Android Studio understand these sneaky friendships.

Note, any use of internal types is completely at your own risk, they are hidden on purpose, and thus you might break the library or the library owners will break you on the library update.