Binding Annotations with Guice and Kotlin

If you’re using kotlin-guice to use Guice with your Kotlin project, you might have run into problems with binding annotations. Annotations let you use a specific implementation of an interface when there are multiple implementations registered with Guice:
@Singleton
class HitLogger @Inject constructor(@LogTableSource val table: Table) {
    fun log() = println(table.name)
}

@Singleton
class TriggerLogger @Inject constructor(@TriggerTableSource val table: Table) {
    fun log() = println(table.name)
}

The problem is Kotlin works slightly differently when it comes to annotations and figuring out how to make it work can be tricky. The documentation at Kotlin references multiple different target types for constructor parameters, but some are invisible to Java and others sound correct but won’t work with Guice. The correct annotation for a Guice constructor is as follows:
@BindingAnnotation
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogTableSource

Thanks to Kotlin you don’t have to worry about overriding equals() and hashCode(). A full example implementation is below.

Full sample code


package main

import com.authzee.kotlinguice4.KotlinModule
import com.google.inject.BindingAnnotation
import com.google.inject.Guice
import com.google.inject.Inject
import com.google.inject.Singleton

@BindingAnnotation
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogTableSource

@BindingAnnotation
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class TriggerTableSource

interface Table { val name: String }

class HitTable : Table {
    override val name = "Hit Table"
}

class TriggerTable : Table {
    override val name = "Trigger Table"
}

@Singleton
class HitLogger @Inject constructor(@LogTableSource val table: Table) {
    fun log() = println(table.name)
}

@Singleton
class TriggerLogger @Inject constructor(@TriggerTableSource val table: Table) {
    fun log() = println(table.name)
}

class TableModule : KotlinModule() {
    override fun configure() {
        bind<Table>().annotatedWith<LogTableSource>().toInstance(HitTable())
        bind<Table>().annotatedWith<TriggerTableSource>().toInstance(TriggerTable())
        bind<HitLogger>()
        bind<TriggerLogger>()
    }
}

fun main() {
    val injector = Guice.createInjector(TableModule())

    injector.getInstance(HitLogger::class.java).log()
    injector.getInstance(TriggerLogger::class.java).log()
}

Output

Hit Table
Trigger Table

Process finished with exit code 0