kapt: Annotation Processing for Kotlin

This post is largely outdated.
Please check out Better Annotation Processing: Supporting Stubs in kapt

As there have been many requests to support Java Annotation Processing, we are working on it, and first results are ready for preview. This is the call for early feedback.

We are planning to release the initial support for JSR 269 Annotation Processing in M12, which is planned for the end of May. Meanwhile, the bulk of the work is already done, and you can test it by using the SNAPSHOT version of the Kotlin plugin for Gradle. The support is rather limited, but Dagger 2 works :)

Annotation Processing Basics

JSR 269 defines an API for a special kind of plugins for the Java compiler, annotation processors. Such a plugin can ask the compiler, roughly, “what code elements (classes, methods, fields) are annotated with @Foo?” The compiler returns a collection of objects that represent annotated elements. Then, the processor can inspect them and generate some new code that will be compiled during the same pass as the annotated code. The trick is that the generated code can be used by the hand-written code although it did not exist when the compiler started working.

To support Annotation Processing in a language other than Java, one has a few options:

One. Re-implement the JSR 269 APIs. This requires some work, but is not terribly hard. The problem is that it does not helped in a mixed project: in the end we need to process annotated element from both Java and Kotlin code, and only supporting JSR 269 in Kotlin is not that much of a gain.

Two. Generate Java sources from Kotlin sources and then feed them into the Java compiler that in turn runs the processors. Of course, it’s too hard to translate Kotlin to completely working Java code (translation of method bodies would be extremely painful), but all that’s really needed is just declarations. This is the way Groovy does it: by generating Java “stubs” from Groovy code and feeding them to the Java compiler. This would work for Kotlin too, but requires two runs of the compiler: first to generate stubs, and second to compile fully against generated code. Referring to classes generated by the processors is possible, but there will be issues in some cases (inferring property/function types from the right-hand-side expressions that use generated code, while generating stubs).

Three. Pretend that Kotlin binaries are Java sources. Normally, the Kotlin compiler runs first and the Java compiler sees the Kotlin code as binary .class files. Of course, all code elements, both source and binary, are represented uniformly inside the Java compiler. So instead of getting only annotated Java sources, the processor may also get annotated Kotlin binaries, it won’t notice the difference through the available API. Unfortunately, Javac won’t do that automatically, but we can plug in between the Java compiler and the annotation processor, find the binary elements ourselves and add them to the source elements normally returned by javac. A huge advantage is that this solution is rather easy to implement (involves a little bytecode generation, but we are kind of used to it:) ). There are a few important limitations, though: Kotlin code can not refer to the declarations generated by the processor and source-retained annotations are not visible through binaries. (Update: both limitations have been lifted later on.)

For now, we went for the option three under then name of kapt (Kotlin Annotation Processing). It looks like it enables the most important real-life use cases. We may support option two later, though.

Example: Using Dagger 2

kapt is supported by the Gradle plugin, to enable it, add

to your build script. Annotations will be picked up from both your Java and Kotlin code.

Since kapt is not released yet, it is only available from the SNAPSHOT version of Kotlin:

Due to limitations mentioned above, the class(es) using code generated by Dagger have to be written in Java (normally, it is very little code):

Everything else can be written in Kotlin. Here’s an example project demonstrating usage of Dagger: kotlin-dagger (you may need to enforce re-downloading of snapshot artifacts).

Feedback

We’d really appreciate if you tried kapt in your project and gave us feedback:

  • What frameworks did you try it with?
  • Did it work for you?
  • How much Java code did you have to write?
  • Any other issues?

About Andrey Breslav

Andrey is the lead language designer of Kotlin at JetBrains.
This entry was posted in Android, Tools. Bookmark the permalink.

21 Responses to kapt: Annotation Processing for Kotlin

  1. Eddie Ringle says:

    Building with 1.2.3 of the Android Gradle plugin fails. The kotlin-dagger sample uses 1.1.0.

    Error:

  2. Даниил Водопьян says:

    @Andrey, I am a little confused by your explanation of limitations for the third option. I guess I don’t know the compilation process well enough. What documentation did you use? Don’t know what to google for. Could you please give me to a starting point?

  3. cypressious says:

    This is great news! Will M12 also contain the possibility to annotate the setter method of delegated properties? This way we could inject vars delegated to notNull().

  4. Mickele Moriconi says:

    Nice work guys, this is definitely the last barrier! :)

    So, I tried to use DBFlow (https://github.com/Raizlabs/DBFlow) but couldn’t make it work. I changed the Android Gradle plugin version as Eddie Ringle said previously.

    Error:


    FAILURE: Build failed with an exception.

    What went wrong:
    Execution failed for task ':app:compileDebugJava'.

    java.lang.NoSuchMethodError: kotlin.KotlinPackage.split$default(Ljava/lang/String;[CZII)Ljava/util/List;

    Try:
    Run with --debug option to get more log output.
    Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugJava'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
    at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:42)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
    at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:305)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.executeTask(AbstractTaskPlanExecutor.java:79)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:63)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:51)
    at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:23)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:88)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:29)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
    at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
    at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:68)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:55)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:80)
    at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:33)
    at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:24)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:36)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
    at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:51)
    at org.gradle.internal.Actions$RunnableActionAdapter.execute(Actions.java:171)
    at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:237)
    at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:210)
    at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:35)
    at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:24)
    at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:206)
    at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:169)
    at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
    at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
    at org.gradle.launcher.Main.doAction(Main.java:33)
    at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
    at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:54)
    at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:35)
    at org.gradle.launcher.GradleMain.main(GradleMain.java:23)
    at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:33)
    at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:130)
    at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:48)
    Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodError: kotlin.KotlinPackage.split$default(Ljava/lang/String;[CZII)Ljava/util/List;
    at com.sun.tools.javac.main.Main.compile(Main.java:553)
    at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
    at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138)
    at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:42)
    at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:35)
    at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.delegateAndHandleErrors(NormalizingJavaCompiler.java:97)
    at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:50)
    at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:36)
    at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:34)
    at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:25)
    at org.gradle.api.tasks.compile.JavaCompile.performCompilation(JavaCompile.java:158)
    at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:138)
    at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:92)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:235)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:211)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:222)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:200)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 47 more
    Caused by: java.lang.NoSuchMethodError: kotlin.KotlinPackage.split$default(Ljava/lang/String;[CZII)Ljava/util/List;
    at org.jetbrains.kotlin.annotation.KotlinAnnotationProvider.readAnnotations(KotlinAnnotationProvider.kt:56)
    at org.jetbrains.kotlin.annotation.KotlinAnnotationProvider.access$readAnnotations$0(KotlinAnnotationProvider.kt:22)
    at org.jetbrains.kotlin.annotation.KotlinAnnotationProvider$annotatedKotlinElements$1.invoke(KotlinAnnotationProvider.kt:34)
    at org.jetbrains.kotlin.annotation.KotlinAnnotationProvider$annotatedKotlinElements$1.invoke(KotlinAnnotationProvider.kt:22)
    at kotlin.properties.LazyVal.get(Delegation.kt:169)
    at org.jetbrains.kotlin.annotation.KotlinAnnotationProvider.getAnnotatedKotlinElements(KotlinAnnotationProvider.kt)
    at org.jetbrains.kotlin.annotation.AnnotationProcessorWrapper.process(AnnotationProcessorWrapper.kt:94)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:794)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:705)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176)
    at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170)
    at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:856)
    at com.sun.tools.javac.main.Main.compile(Main.java:523)
    ... 66 more

    BUILD FAILED

    I tried using both annotated Java and Kotlin classes.

    Hope it helps, thanks.

    • Yan Zhulanow says:

      Can you please send a test project to yan.zhulanow [at] jetbrains.com?

      • Mickele Moriconi says:

        Done. Also if anyone is interested, I created a repository on GitHub with the source: https://github.com/mickele/kotlin-poc

        The master branch is a Kotlin + Java (android-apt) that is compiling and working.
        The kapt branch is the pure Kotlin + kapt that I’m having some issues with.

  5. vh says:

    Will this enable the use of “active annotations”, e.g. that i can annotate a field as a javafx property and it automatically generates a javafx property class with bindings (akin to Xtend?)

    • Yan Zhulanow says:

      You can’t modify Kotlin classes using Annotation Processors, as Active Annotations do.

      • vh says:

        Which I always found limiting. The data annotation in Kotlin is a prime example of what I’m talking. Just make this process available as an API

  6. Paul says:

    What about QueryDSL support?

  7. Dale King says:

    This will not support AndroidAnnotations project which generates subclasses of annotated classes that you need to reference from other code.

  8. David Leppik says:

    Having just educated myself on what Dagger and Dagger 2 are, it seems to me that this is the right direction, but ultimately not far enough. My philosophy (and one which resonates well with Kotlin) is that boilerplate is a sign that the language is missing a feature. Dagger 2 writes DI boilerplate for you.

    This is too big a project for Kotlin 1.0, but there’s clearly a need for language features that do DI at compile time, so you don’t need a separate code generator like Dagger 2.

  9. ion says:

    in kotlin-dagger example is used ‘kotlin-android’,

    tried to use with gradle ‘kotlin’ plugin,
    dependencies {
    compile ‘org.jetbrains.kotlin:kotlin-stdlib:0.12.213’,
    ‘com.google.dagger:dagger:2.0’,
    kapt ‘com.google.dagger:dagger-compiler:2.0’
    }

    it doesn’t understand ‘kapt’

    Is this android only feature ???

  10. Vitaliy says:

    As I see from this example kotlin-dagger little java wrapper for DaggerApplicationComponent is not a problem at all.
    Unpleasant restriction is that you need to use next line everywhere
    (getApplication() as DemoApplication).component().inject(this)

  11. Martynas says:

    Everything worked like charm. So far… I just couldn’t make it to work in tests.
    DaggerTestSomeComponent was not generated from TestSomeComponent : SomeComponent. I’ve tried various combinations. Including creating absolutely new component just in tests. Still nothing. Is there any workaround or could I provide some other details?

  12. yanex says:

    Please try to add a test kapt dependency:

    kaptTest “groupId:artifactId:version”.

Comments are closed.