Table of Contents
Kotlin support in PMD is based on the official grammar from https://github.com/Kotlin/kotlin-spec.
Language Info for Kotlin
- Since PMD 7.0
- Implementation:
KotlinLanguageModule - Id: kotlin
- PMD: ✔️
- CPD: ✔️
Java-based rules and XPath-based rules are supported.
Kotlin language versions / feature support
PMD’s Kotlin parser may lag behind the latest Kotlin compiler and does not aim to implement all new language features immediately.
The only Kotlin 2.x feature currently supported is “Multidollar interpolation: improved handling of $ in string literals”. This feature was preview in Kotlin 2.1.0 and stabilized in Kotlin 2.2.0. See Multi-dollar string interpolation.
For informational purposes, PMD advertises Kotlin 2.2.0 as the highest supported version. Selecting a different Kotlin language version does not currently change the parser behavior (unlike Java).
Other Kotlin 2.0.0 / 2.1.0 / 2.2.0 / 2.3.0 language features are not supported at this time. PRs to improve Kotlin parser coverage are welcome.
Providing the auxiliary classpath
The auxiliary classpath (or short “auxClasspath”) is configured via the
Language Property “auxClasspath”.
It is a string containing multiple paths separated by either a colon (:) under Linux/MacOS
or a semicolon (;) under Windows. This property can be provided on the CLI with parameter
--aux-classpath.
Not providing the correct auxClasspath might result in false positives or negatives for some rules, that depend on resolved types.
Note for rule testing (rule test XML, PMD Designer): these typically analyze one isolated Kotlin snippet/file at a time with no
auxClasspathconfigured. In that setup, cross-packageimport-based type resolution to another Kotlin source file does not work yet – only same-package cross-file resolution and same-file resolution do. If a test snippet imports a type from a different package, resolve it either by putting both types in the same package, or by configuringauxClasspathto include the type as a compiled class (a jar, or another module’starget/classes), which resolves correctly regardless of package.
Type resolution for Kotlin is powered by kotlin-type-mapper, which is bundled with PMD.
XPath rule support
Kotlin provides XPath-queryable attributes and helper functions for type-aware rules.
Type-info attributes
The following attributes depend on type resolution: they are only available when auxClasspath
is configured and the kotlin-type-mapper analysis has resolved the types.
| Attribute | Nodes | Meaning |
|---|---|---|
@TypeName |
PropertyDeclaration, ClassParameter, FunctionValueParameter, CatchBlock, ForStatement, ClassDeclaration, DelegationSpecifier, UnescapedAnnotation, SingleAnnotation |
Fully-qualified type name (including generic type arguments and nullable marker, e.g. kotlin.collections.List<kotlin.String>?) |
@ReturnTypeName |
FunctionDeclaration |
Fully-qualified return type name (including generic type arguments and nullable marker) |
@AnnotationFqNames |
FunctionDeclaration, ClassDeclaration, PropertyDeclaration, ClassParameter |
Sequence of FQNs of all annotations on the declaration |
A type-info attribute is absent (not present with a null value) whenever its value is unavailable.
General attributes
These attributes are structural: they come directly from the parsed source and don’t depend on
type resolution, so they’re always present regardless of auxClasspath.
| Attribute | Nodes | Meaning |
|---|---|---|
@Mutable |
PropertyDeclaration |
true for var, false for val. |
@Identifier |
ClassDeclaration, FunctionDeclaration, ClassParameter, CompanionObject, VariableDeclaration, ImportAlias |
Simple name of the declared identifier |
@Name |
ImportHeader |
Fully-qualified imported name (e.g. kotlin.collections.listOf). |
Note:
@TypeNameand@ReturnTypeNameuse fully-qualified names for generic type arguments:kotlin.collections.List<kotlin.String>?, notList<String>?. When comparing in XPath, use the full FQN form:@TypeName = 'kotlin.collections.List<kotlin.String>'. For type checks that ignore generics, preferpmd-kotlin:typeIs()orpmd-kotlin:typeIsExactly().
Note:
VariableDeclarationcarries only@Identifier(the variable name). Modifiers likeprivate,lateinit, orconstare on the parentPropertyDeclarationnode. Use//PropertyDeclaration[pmd-kotlin:modifiers() = 'private']rather than queryingVariableDeclaration.
Note: Boolean attributes like
@Mutablerequire XPath’strue()/false()functions://PropertyDeclaration[@Mutable=false()](immutablevaldeclarations). Usingfalsewithout parentheses matches a node name, not a boolean value.
Absent value: unresolved vs genuinely none
An absent attribute is ambiguous on its own — the type could be unresolved, or genuinely not
present. Note that a configured auxClasspath does not guarantee every type resolves (incomplete
classpath, generated/preprocessed code, missing annotation processors, etc.).
pmd-kotlin:hasUnresolvedReference() — whether the node has an unresolved reference (the Kotlin
analog of pmd-java’s isUnresolved()) — disambiguates the two cases:
hasUnresolvedReference() |
Attribute absent means |
|---|---|
| true | unresolved — analysis ran but the type could not be resolved |
| false | genuinely none — analysis ran and resolved; there is no such value |
(: property definitely without a resolvable declared type (not merely unresolved) :)
//PropertyDeclaration[not(@TypeName) and not(pmd-kotlin:hasUnresolvedReference())]
Limitation:
hasUnresolvedReference()only checks the context node’s begin line and only returns a boolean — it does not tell you which symbol failed to resolve, and it won’t see an unresolved reference on a different line of a multi-line declaration. Exposing the unresolved reference name(s) is tracked as a possible follow-up function.
XPath functions
There are a number of XPath functions available in the pmd-kotlin namespace, e.g.
typeIs(), isNullable(), hasAnnotation(), and more. For the full list with descriptions,
parameters, and examples, see the
PMD extension functions
page.
Java-based rules
The same type information is accessible from Java-based rules via public interfaces and
KotlinNodeTypeData:
import net.sourceforge.pmd.lang.kotlin.ast.HasTypeName;
import net.sourceforge.pmd.lang.kotlin.ast.HasModifiers;
import net.sourceforge.pmd.lang.kotlin.ast.KotlinParser.KtKotlinFile;
import net.sourceforge.pmd.lang.kotlin.types.KotlinNodeTypeData;
import net.sourceforge.pmd.lang.kotlin.types.KotlinTypeName;
// Check type on a node that implements HasTypeName
// (PropertyDeclaration, FunctionDeclaration, ClassParameter, etc.)
if (node instanceof HasTypeName) {
KotlinTypeName type = ((HasTypeName) node).getType(); // null if unresolved
if (type != null) {
String fqName = type.getFqName(); // e.g. "kotlin.collections.List"
boolean nullable = type.isNullable(); // true if declared as nullable (?)
String display = type.toDisplayString(); // e.g. "kotlin.collections.List<kotlin.String>?"
}
}
// Get modifiers on declaration nodes implementing HasModifiers
// (Java-only API: not exposed as @Modifiers XPath attribute; use
// pmd-kotlin:modifiers() from XPath instead)
if (node instanceof HasModifiers) {
List<String> mods = ((HasModifiers) node).getModifiers(); // e.g. ["override", "suspend"]
}
// Static helpers on KotlinNodeTypeData (work on any KotlinNode)
KotlinTypeName type = KotlinNodeTypeData.getType(node);
KotlinTypeName returnType = KotlinNodeTypeData.getReturnType(node);
List<String> annotations = KotlinNodeTypeData.getAnnotationFqNames(node);
All type getters return null when type analysis has not run or the type could not be resolved.
The KotlinTypeName class provides structured access to the FQ name, nullability, resolution
status, and full display string (including generic type arguments).
Note: Unlike pmd-java’s
TypeNode.getTypeMirror()which returns richJTypeMirrorobjects (never null, usingUNKNOWNfor unresolved types), pmd-kotlin exposesKotlinTypeNamewith string-based type names — reflecting the simpler output of kotlin-type-mapper. A richer type model may be added in a future version.