A Maven plugin for running Kotlin Symbol Processing (KSP) on JVM projects.
This plugin integrates KSP (Kotlin Symbol Processing) into Maven builds, allowing you to process Kotlin source files with annotation processors that use the KSP API.
Check out the blog post.
References:
- Dual Scope Processing: Separate goals for main sources (
process) and test sources (process-test) - Thread Safe: Fully supports Maven parallel builds (
mvn -T) - Scope-Aware Logging: Distinct log prefixes for main (
[ksp:main]) and test ([ksp:test]) processing - Incremental Compilation: Optional incremental processing support
- Automatic Configuration: Automatically detects Kotlin version, JVM target, and other settings from the project environment
- Flexible Configuration: Extensive configuration options for all KSP parameters
The plugin provides two goals:
process: Processes main sources in thegenerate-sourcesphaseprocess-test: Processes test sources in thegenerate-test-sourcesphase
Minimal setup for processing main sources only:
<properties>
<kotlin.version>2.3.10</kotlin.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>me.kpavlov.ksp.maven</groupId>
<artifactId>ksp-maven-plugin</artifactId>
<version>[LATEST VERSION]</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<!-- KSP processors are plugin dependencies, not project dependencies -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-ksp-processor</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
<sourceDirectory>src/main/kotlin</sourceDirectory>
</build>To process both main and test sources, use <extensions>true</extensions> to activate both goals automatically:
<properties>
<kotlin.version>2.3.10</kotlin.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>me.kpavlov.ksp.maven</groupId>
<artifactId>ksp-maven-plugin</artifactId>
<version>[LATEST VERSION]</version>
<extensions>true</extensions>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-ksp-processor</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<phase>compile</phase>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<phase>test-compile</phase>
</execution>
</executions>
</plugin>
</plugins>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
</build>All available configuration options:
<plugin>
<groupId>me.kpavlov.ksp.maven</groupId>
<artifactId>ksp-maven-plugin</artifactId>
<version>[LATEST VERSION]</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Main source directory to process (default: ${project.build.sourceDirectory}) -->
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<!-- Additional source directories (default: none) -->
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/generated/kotlin</sourceDir>
</sourceDirs>
<!-- Output directory for generated Kotlin sources (default: ${project.build.directory}/generated-sources/ksp) -->
<kotlinOutputDir>${project.build.directory}/generated-sources/ksp</kotlinOutputDir>
<!-- Output directory for generated Java sources (default: ${project.build.directory}/generated-sources/ksp) -->
<javaOutputDir>${project.build.directory}/generated-sources/ksp</javaOutputDir>
<!-- Output directory for compiled classes (default: ${project.build.directory}/ksp-classes) -->
<classOutputDir>${project.build.directory}/ksp-classes</classOutputDir>
<!-- Output directory for resources (default: ${project.build.directory}/generated-resources/ksp) -->
<resourceOutputDir>${project.build.directory}/generated-resources/ksp</resourceOutputDir>
<!-- KSP output directory (default: ${project.build.directory}/ksp) -->
<kspOutputDir>${project.build.directory}/ksp</kspOutputDir>
<!-- Cache directory for incremental processing (default: ${project.build.directory}/ksp-cache) -->
<cachesDir>${project.build.directory}/ksp-cache</cachesDir>
<!-- Enable incremental processing (default: false) -->
<incremental>true</incremental>
<!-- Enable incremental compilation logging (default: false) -->
<incrementalLog>true</incrementalLog>
<!-- Kotlin language version (default: detected from kotlin-maven-plugin or kotlin.version) -->
<languageVersion>2.3</languageVersion>
<!-- Kotlin API version (default: detected from languageVersion) -->
<apiVersion>2.3</apiVersion>
<!-- JVM default mode for interfaces (default: detected from kotlin-maven-plugin) -->
<jvmDefaultMode></jvmDefaultMode>
<!-- KSP processor options as key-value pairs (default: none) -->
<processorOptions>
<option1>value1</option1>
<option2>value2</option2>
</processorOptions>
<!--
Glob patterns to include specific SymbolProcessorProvider classes (default: all included).
Use '*' for a single package segment, '**' for any depth.
When non-empty, only providers whose fully-qualified class name matches at least
one pattern are passed to KSP.
-->
<processorIncludes>
<processorInclude>com.example.annotation.*</processorInclude>
</processorIncludes>
<!--
Glob patterns to exclude specific SymbolProcessorProvider classes (default: none excluded).
Providers matching any exclude pattern are removed even if they match an include pattern.
-->
<processorExcludes>
<processorExclude>com.example.SlowProcessor</processorExclude>
</processorExcludes>
<!-- Continue build on processing errors (default: false) -->
<ignoreProcessingErrors>false</ignoreProcessingErrors>
<!-- Treat all warnings as errors (default: false) -->
<allWarningsAsErrors>false</allWarningsAsErrors>
<!-- Map annotation arguments in Java sources (default: true) -->
<mapAnnotationArgumentsInJava>true</mapAnnotationArgumentsInJava>
<!-- Module name (default: ${project.artifactId}) -->
<moduleName>my-module</moduleName>
<!-- JVM target version (default: detected from kotlin-maven-plugin or maven.compiler.release) -->
<jvmTarget>17</jvmTarget>
<!-- Skip KSP processing (default: false, can be set via -Dksp.skip=true) -->
<skip>false</skip>
<!-- Add generated sources to compilation (default: true) -->
<addGeneratedSourcesToCompile>true</addGeneratedSourcesToCompile>
<!-- Enable debug output (default: false) -->
<debug>false</debug>
</configuration>
<!-- KSP processors are plugin dependencies, not project dependencies -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-ksp-processor</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</plugin>KSP processors normally decide for themselves which classes to process. However, you can restrict
which SymbolProcessorProvider implementations are activated using glob-style include and exclude
patterns matched against the provider's fully-qualified class name.
Both processorIncludes and processorExcludes are List<String> parameters.
In Maven XML configuration, each list element uses the singular form of the parameter name
as the child tag (i.e. <processorInclude> inside <processorIncludes>, and
<processorExclude> inside <processorExcludes>).
Pattern syntax:
| Token | Meaning |
|---|---|
* |
Any sequence of characters within a single package segment (no dots) |
** |
Any sequence of characters across package segments (including dots) |
? |
Any single non-dot character |
| other | Matched literally |
Include/exclude semantics:
- When
processorIncludesis empty, all discovered providers pass the include check. - A provider is retained only when it satisfies the include check and does not match any
processorExcludespattern. - Excludes take priority: a provider that matches both an include and an exclude pattern is removed.
Example — run only one specific processor:
<configuration>
<processorIncludes>
<processorInclude>com.example.MyProcessor</processorInclude>
</processorIncludes>
</configuration>Example — exclude a slow or unwanted processor while keeping all others:
<configuration>
<processorExcludes>
<processorExclude>com.example.SlowProcessor</processorExclude>
</processorExcludes>
</configuration>Example — include a whole package but exclude one class within it:
<configuration>
<processorIncludes>
<processorInclude>com.example.annotation.*</processorInclude>
</processorIncludes>
<processorExcludes>
<processorExclude>com.example.annotation.ExperimentalProcessor</processorExclude>
</processorExcludes>
</configuration>The plugin automatically detects many settings from the project configuration and properties:
| Parameter | Detection Source (in order of priority) |
|---|---|
languageVersion |
<languageVersion> in kotlin-maven-plugin, kotlin.compiler.languageVersion property, or kotlin.version (major.minor) |
apiVersion |
<apiVersion> in kotlin-maven-plugin, kotlin.compiler.apiVersion property, or languageVersion |
jvmTarget |
<jvmTarget> in kotlin-maven-plugin, kotlin.compiler.jvmTarget, maven.compiler.release, or maven.compiler.target |
jdkHome |
<jdkHome> in kotlin-maven-plugin, kotlin.compiler.jdkHome, or java.home system property |
allWarningsAsErrors |
<allWarningsAsErrors> in kotlin-maven-plugin, or kotlin.compiler.allWarningsAsErrors property |
Many parameters can be set via command line or Maven properties:
ksp.skip: Skip KSP processing for main sources (-Dksp.skip=true)ksp.skipTest: Skip KSP processing for test sources (-Dksp.skipTest=true)ksp.debug: Enable debug output (-Dksp.debug=true)kotlin.compiler.languageVersion: Set Kotlin language versionkotlin.compiler.jvmTarget: Set JVM target version
The plugin uses different output directories for main and test processing:
- Kotlin/Java sources:
${project.build.directory}/generated-sources/ksp - Classes:
${project.build.directory}/ksp-classes - Resources:
${project.build.directory}/generated-resources/ksp - KSP working directory:
${project.build.directory}/ksp - Cache:
${project.build.directory}/ksp-cache
- Kotlin/Java sources:
${project.build.directory}/generated-test-sources/ksp - Classes:
${project.build.directory}/ksp-test-classes - Resources:
${project.build.directory}/generated-test-resources/ksp - KSP working directory:
${project.build.directory}/ksp-test - Cache:
${project.build.directory}/ksp-test-cache
All directories can be customized via configuration parameters.
The plugin integrates with Maven's standard lifecycle phases:
generate-sourcesphase: KSP processors run on main sources- Generated sources automatically added to compilation source roots
compilephase: Kotlin compiler compiles both original and generated sources
generate-test-sourcesphase: KSP processors run on test sources- Generated test sources automatically added to test compilation source roots
test-compilephase: Kotlin compiler compiles both original and generated test sources
The plugin is fully thread-safe and supports Maven's parallel build execution:
# Build with 4 parallel threads
mvn clean install -T4
# Build using 1 thread per CPU core
mvn clean install -T1CEach execution creates isolated instances of KotlinSymbolProcessing with no shared mutable state, ensuring safe
concurrent builds in multi-module projects.
For detailed information about thread safety guarantees, see PARALLEL_EXECUTION.md.
Skip main source processing via command line:
mvn clean install -Dksp.skip=trueOr in your pom.xml:
<configuration>
<skip>true</skip>
</configuration>Skip test source processing via command line:
mvn clean test -Dksp.skipTest=trueOr in your pom.xml for the process-test execution:
<execution>
<id>process-test-sources</id>
<goals>
<goal>process-test</goal>
</goals>
<configuration>
<skipTest>true</skipTest>
</configuration>
</execution>If you see "No KSP processors found in dependencies":
- Verify your processor is added as a plugin dependency (inside
<plugin><dependencies>section), not as a project dependency - Check the processor JAR contains
META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider - Ensure the dependency scope is not
test
If the Kotlin compiler can't find generated sources:
- Verify
addGeneratedSourcesToCompileistrue(default) - Check that KSP plugin runs before Kotlin compilation
- Verify output directories are correct
If incremental compilation causes problems:
- Disable it:
<incremental>false</incremental> - Clean the cache:
mvn clean - Delete
${project.build.directory}/ksp-cache
Enable debug output to see detailed processing information:
<configuration>
<debug>true</debug>
</configuration>This will log:
- Found KSP processors
- Processor classloader details
- Processor providers
- KSP configuration
- Incremental changes (if enabled)
Log messages are prefixed with scope identifiers:
[ksp:main]for main source processing[ksp:test]for test source processing
Contributions are welcome. Follow the Kotlin coding conventions and ensure all tests pass before submitting a pull request.
Build, verify, install the plugin and test with sample project:
make buildFormat code:
make formatRun linting:
make lintGenerate API documentation:
make apidocsRun all checks (format, lint, build):
make allBuild and install the plugin:
mvn clean installTest with the sample project:
cd sample-project
mvn clean compile