Skip to main content
Version: Next

Migration guide to 2.0.0

This page is a migration guide from detekt 1.x to detekt 2.x. It is split in two sections:

  • User guide — for people consuming detekt through the Gradle plugin or the CLI.
  • Rule author guide — for people who maintain a custom rule set, a custom report, or any other detekt extension.

If you find a missing or unclear step while migrating, please open an issue or — even better — a pull request to improve this page.

Why so many changes?

detekt 1.0 shipped in 2019 and we kept backwards compatibility for the entire 1.x line. The 2.x line is our opportunity to clean up the API, drop the K1 (legacy) Kotlin compiler in favour of the official Kotlin Analysis API, and align with modern Gradle and Android Gradle Plugin versions. Most of the changes below are mechanical search-and-replace; the only conceptually new piece is the Analysis API for rule authors.


User guide​

Minimum supported versions​

Tooldetekt 1.23.xdetekt 2.0.x
Gradle6.8.38.14
Android Gradle Plugin4.x8.8.2

detekt itself is built and tested against the latest JDK 25, Kotlin 2.4, Gradle 9.x and AGP 9.x. See the compatibility table for the exact versions each release was built with.

Most detekt artifacts still target JVM 8 bytecode, with one exception: detekt-rules-ktlint-wrapper targets JVM 17, in preparation for the next ktlint major release. If you use the ktlint wrapper, detekt must run on JDK 17 or newer.

Gradle plugin IDs​

The main plugin ID changed from io.gitlab.arturbosch.detekt to dev.detekt:

plugins {
- id("io.gitlab.arturbosch.detekt") version "1.23.8"
+ id("dev.detekt") version "2.0.0"
}

If you used the legacy apply plugin: form, replace "io.gitlab.arturbosch.detekt" with "dev.detekt" the same way.

There are now two plugin IDs:

Plugin IDPurpose
dev.detektThe main plugin — registers all analysis tasks. Use this one.
dev.detekt.gradle.baseOnly the detekt {} extension and shared conventions, no tasks.

The 1.x compiler plugin ID io.github.detekt.gradle.compiler-plugin has no successor — see The compiler plugin was removed.

Maven coordinates change​

All published artifacts moved to the dev.detekt group. In 1.x, artifacts were split across io.gitlab.arturbosch.detekt and — for a few modules — io.github.detekt. Both are now dev.detekt.

-implementation("io.gitlab.arturbosch.detekt:detekt-api:1.23.8")
+implementation("dev.detekt:detekt-api:2.0.0")

Module renames​

Several modules were renamed to make their purpose clearer:

1.x module2.x module
detekt-formattingdetekt-rules-ktlint-wrapper
detekt-rules-documentationdetekt-rules-comments
detekt-rules-emptydetekt-rules-empty-blocks
detekt-rules-errorpronedetekt-rules-potential-bugs
detekt-report-xmldetekt-report-checkstyle
detekt-report-mddetekt-report-markdown

detekt-report-txt and detekt-sample-extensions were removed. detekt-test was split — see Testing rules.

If you referenced any of these artifacts directly (for example through detektPlugins(...) for the formatting wrapper), update the coordinates accordingly:

dependencies {
- detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8")
+ detektPlugins("dev.detekt:detekt-rules-ktlint-wrapper:2.0.0")
}

The detekt extension is now lazy​

DetektExtension is an interface whose members are all Gradle Property / ListProperty / ConfigurableFileCollection types. It no longer extends CodeQualityExtension, and the long-deprecated reports {} block on the extension is gone — configure reports on the tasks instead.

You can keep using = thanks to Gradle's lazy property assignment, or use .set(...):

detekt {
- parallel = true
- buildUponDefaultConfig = true
+ parallel.set(true)
+ buildUponDefaultConfig.set(true)
}

baseline is now a RegularFileProperty and basePath a DirectoryProperty, so they take File/Directory values rather than File?/String?.

maxIssues is replaced by failOnSeverity​

The weight-based build { maxIssues } mechanism is gone. detekt now fails the build when it finds an issue at or above a configured severity, which defaults to Error:

detekt {
- // build.maxIssues in detekt.yml
+ failOnSeverity.set(FailOnSeverity.Error) // Info | Warning | Error | Never
}

Use FailOnSeverity.Never for the old "never fail" behaviour. On the CLI this is --fail-on-severity, and --max-issues was removed.

Severity is configured per rule in detekt.yml through the severity key, and the severity values are now only Info, Warning and Error.

New and renamed Gradle tasks​

detekt 2.x registers analysis tasks both per Kotlin compilation and per Kotlin source set:

TaskAnalysis modeNotes
detektlightPlain task, wired to check. Unchanged.
detektMain, detektTest, …fullOne per Kotlin compilation. Has a classpath.
detektMainSourceSet, detektTestSourceSet, …lightNew. One per Kotlin source set.
detektBaselineMain, detektBaselineMainSourceSet, …—Baseline counterparts of the above.

On Android you also get detekt<Variant> per build variant, with detektMain and detektTest acting as aggregate lifecycle tasks across variants. On Kotlin Multiplatform, compilation tasks are named detekt<Compilation><Target> (e.g. detektMainJvm), and only JVM and Android targets get full analysis. See Using Type Resolution for the full task list.

Baseline files for the new source-set tasks get a SourceSet suffix, e.g. detekt-baseline-mainSourceSet.xml.

If you had custom Detekt task registrations purely to wire up classpath and jvmTarget, delete them — the generated compilation tasks already do it for you.

The Worker API is still opt-in, unchanged from 1.x. Enable it with detekt.use.worker.api=true in gradle.properties.

Analysis modes: light and full​

Type-aware rules are no longer gated behind an opt-in annotation, but they still need compiler information. detekt now has an explicit analysis mode:

  • light — only the PSI/AST is available. Rules that need type information do not run.
  • full — the Kotlin Analysis API is available and all rules run.

The Gradle plugin picks the mode for you: a task with a non-empty classpath runs in full mode, otherwise light. On the CLI, pass --analysis-mode full together with --classpath. The default is light.

This replaces the 1.x @RequiresTypeResolution opt-in and the BindingContext plumbing.

note

Rules skipped in light mode do not fail the run — they are simply marked inactive. detekt logs The rule '…' requires type resolution but it was run without it. only when --debug is enabled. If a rule you expect seems to have stopped reporting after the upgrade, re-run with --debug (or detekt { debug = true }) and check the analysis mode.

Reports​

The plain-text (txt) report was removed entirely.

Format ID renames​

The IDs used on the CLI and Gradle plugin for the XML and Markdown reports changed:

1.x report id2.x report id
xmlcheckstyle
mdmarkdown
-detekt --report xml:report.xml --report md:report.md
+detekt --report checkstyle:report.xml --report markdown:report.md

In Gradle:

tasks.withType<Detekt>().configureEach {
reports {
- xml.required.set(true)
+ checkstyle.required.set(true)
- md.required.set(true)
+ markdown.required.set(true)
}
}

First-party plugin reports​

The complexity and statistics reports moved from core to first-party plugins. Add the plugins to your Gradle dependencies if you use these reports:

dependencies {
detektPlugins("dev.detekt:detekt-report-complexity:<detekt-version>")
detektPlugins("dev.detekt:detekt-report-statistics:<detekt-version>")
}

Use a detekt version that includes these plugins (a snapshot until the next alpha is released), and keep the plugin versions aligned with detekt itself.

Configuration file changes​

Run ./gradlew detektGenerateConfig and diff against your existing detekt.yml for an overview of the changes that apply to your project. The most impactful ones:

Removed top-level blocks​

  • build — including maxIssues, excludeCorrectable and weights. See failOnSeverity.
  • output-reports — configure reports through the Gradle plugin or CLI flags instead.

The YAML parser is stricter about types​

Values must now use the right YAML type. 1.x accepted a quoted active: "true" and coerced it; 2.x does not:

style:
MagicNumber:
- active: "true"
+ active: true

Relatedly, config.excludes is a list rather than a comma-separated string:

config:
- excludes: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ['my_rule_set', '.*>.*>[my_property]']

More generally, comma-separated strings are no longer accepted where a list is expected — use proper YAML lists.

Renamed processors and console reports​

If you customised the processors or console-reports exclusion lists, several entries changed name:

1.x name2.x name
ProjectComplexityProcessorProjectCyclomaticComplexityProcessor
FindingsReportIssuesReport
FileBasedFindingsReportFileBasedIssuesReport
LiteFindingsReportLiteIssuesReport

DetektProgressListener and LicenseHeaderLoaderExtension are no longer processors and must be removed from the processors list.

caution

These lists are matched by plain string comparison and are not config-validated, so a stale entry fails open: the processor or console report you meant to exclude silently becomes enabled again. Renaming them is easy to forget and produces no warning.

The formatting rule set is now ktlint​

The ktlint wrapper rule set changed its ID, and its android flag was replaced by code_style and indentStyle:

-formatting:
+ktlint:
active: true
- android: false
+ code_style: 'intellij_idea' # or 'android_studio', 'ktlint_official'
+ indentStyle: 'space'
autoCorrect: true

The DiscouragedCommentLocation rule was removed; many new ktlint rules were added.

The new standard-library rule set​

Rules that report on usages of the Kotlin standard library were spread across complexity, performance, potential-bugs and style. In 2.x they all live in a new standard-library rule set, shipped by the new detekt-rules-standard-library module (part of detekt-rules, so no extra dependency is needed).

If you configured any of the rules below, move them under the standard-library: key in your detekt.yml:

-style:
- UnnecessaryLet:
- active: true
+standard-library:
+ UnnecessaryLet:
+ active: true
Rule1.x rule set
AlsoCouldBeApplystyle
ArrayPrimitiveperformance
CharArrayToStringCallpotential-bugs
CouldBeSequenceperformance
DontDowncastCollectionTypespotential-bugs
DoubleMutabilityForCollectionpotential-bugs
ForEachOnRangeperformance
IteratorHasNextCallsNextMethodpotential-bugs
IteratorNotThrowingNoSuchElementExceptionpotential-bugs
MapGetWithNotNullAssertionOperatorpotential-bugs
MissingUseCallpotential-bugs
MultilineRawStringIndentationstyle
NestedScopeFunctionscomplexity
RedundantHigherOrderMapUsagestyle
ReplaceSafeCallChainWithRuncomplexity
TrimMultilineRawStringstyle
UnnecessaryAnystyle
UnnecessaryApplystyle
UnnecessaryFilterstyle
UnnecessaryLetstyle
UnnecessaryReversedstyle
UseAnyOrNoneInsteadOfFindstyle
UseCheckNotNullstyle
UseCheckOrErrorstyle
UseEmptyCounterpartstyle
UseIfEmptyOrIfBlankstyle
UseIsNullOrEmptystyle
UseLetstyle
UseOrEmptystyle
UseRequirestyle
UseRequireNotNullstyle
UseSumOfInsteadOfFlatMapSizestyle
UselessCallOnNotNullstyle

The rule IDs themselves are unchanged, so baseline files and @Suppress("...") annotations keep working.

Apart from ktlint and standard-library, the rule set keys (comments, complexity, empty-blocks, potential-bugs, …) are unchanged — only the module names moved.

Rule renames​

1.x rule2.x rule
UnusedImportsUnusedImport
UnusedPrivateMemberSplit into UnusedPrivateFunction and UnusedVariable (UnusedPrivateProperty already existed)
UnnecessaryAbstractClassSplit into AbstractClassCanBeConcreteClass and AbstractClassCanBeInterface
CommentOverPrivateFunctionDocumentationOverPrivateFunction
CommentOverPrivatePropertyDocumentationOverPrivateProperty
EmptyKtFileEmptyKotlinFile
MayBeConstMayBeConstant
FunctionMinLengthFunctionNameMinLength
FunctionMaxLengthFunctionNameMaxLength
SpacingBetweenPackageAndImportsSpacingAfterPackageAndImports
UntilInsteadOfRangeToRangeUntilInsteadOfRangeTo
RedundantVisibilityModifierRuleRedundantVisibilityModifier

Update your detekt.yml, baseline files, and any @Suppress("...") annotations referencing the old names.

Rules removed​

PreferToOverPairSyntax and UnnecessaryAnnotationUseSiteTarget were removed. Rules that were already deprecated in 1.x are gone too, most notably:

Removed ruleReplacement
ComplexMethodCyclomaticComplexMethod
MandatoryBracesIfStatementsBracesOnIfStatements with always
OptionalWhenBracesBracesOnWhenStatements
TrailingCommaTrailingCommaOnCallSite / TrailingCommaOnDeclarationSite
DuplicateCaseInWhenExpression, MissingWhenCase, RedundantElseInWhenReported by the Kotlin compiler
ForbiddenPublicDataClass, LibraryCodeMustSpecifyReturnType, LibraryEntitiesShouldNotBePublicThe libraries rule set plugin
ViolatesTypeResolutionRequirements (ruleauthors)Obsolete — RequiresAnalysisApi is enforced by the compiler

The MultiRule mechanism was also removed; if you maintained a custom multi-rule, see the rule author guide below.

Renamed configuration keys​

Threshold-style configs were renamed to allowed…:

RuleOld key(s)New key(s)
LongMethodthresholdallowedLines
LargeClassthresholdallowedLines
LongParameterListfunctionThreshold / constructorThresholdallowedFunctionParameters / allowedConstructorParameters
CyclomaticComplexMethodthresholdallowedComplexity
CognitiveComplexMethodthresholdallowedComplexity
ComplexConditionthresholdallowedConditions
ComplexInterfacethresholdallowedDefinitions
NestedBlockDepththresholdallowedDepth
NestedScopeFunctionsthresholdallowedDepth
MethodOverloadingthresholdallowedOverloads
NamedArgumentsthresholdallowedArguments
CouldBeSequencethresholdallowedOperations
StringLiteralDuplicationthreshold / excludeStringsWithLessThan5CharactersallowedDuplications / allowedWithLengthLessThan
TooManyFunctionsthresholdInFiles, thresholdInClasses, thresholdInInterfaces, thresholdInObjects, thresholdInEnumsallowedFunctionsPerFile, allowedFunctionsPerClass, allowedFunctionsPerInterface, allowedFunctionsPerObject, allowedFunctionsPerEnum

Other key renames:

RuleOld keyNew key
ForbiddenImportimports / forbiddenPatternsforbiddenImports (plus a new allowedImports)
AbsentOrWrongFileLicenselicenseTemplateFilelicenseTemplate
UnderscoresInNumericLiteralsacceptableDecimalLengthacceptableLength
IgnoredReturnValuerestrictToAnnotatedMethodsrestrictToConfig
ForbiddenCommentvalues / customMessagecomments (a list of value/reason pairs)
EmptyFunctionBlockignoreOverriddenFunctionsignoreOverridden
MemberNameEqualsClassNameignoreOverriddenFunctionignoreOverridden
FunctionOnlyReturningConstantexcludeAnnotatedFunctionignoreAnnotated
LateinitUsageexcludeAnnotatedPropertiesignoreAnnotated
UseDataClass, UnnecessaryAbstractClassexcludeAnnotatedClassesignoreAnnotated

The ignoreOverridden option on BooleanPropertyNaming, ConstructorParameterNaming, FunctionNaming, FunctionParameterNaming and VariableNaming was ignored in 1.x and is now removed.

Baselines must be regenerated​

Your 1.x baseline will silently stop working

Baseline files written by detekt 1.x do not work with detekt 2.x. The XML structure is the same, so nothing fails loudly — but the ID format changed, so no 1.x entry matches and every previously suppressed issue is reported again.

The rule ID, the file name and the signature are now separated by : instead of $:

-<ID>TooGenericExceptionCaught:Junk.kt$Junk$e: RuntimeException</ID>
+<ID>TooGenericExceptionCaught:Junk.kt:Junk$e: RuntimeException</ID>

The 1.x signature builder deliberately preserved the old, slightly broken format because fixing it would invalidate every baseline; a major release was the only place it could change. Delete your old baseline and regenerate it with detektBaseline… (Gradle) or --create-baseline (CLI) after upgrading.

If you run the same rule several times with different configurations, the ID carries the rule instance name too, for example FeatureEnvy/my-instance:Junk.kt:signature.

CLI changes​

  • List separators changed. --input, --config, --plugins and --classpath accepted , (and sometimes ;) in 1.x. They now use the platform path separator only: : on *nix, ; on Windows.

    -detekt --input src/main/kotlin,src/test/kotlin
    +detekt --input src/main/kotlin:src/test/kotlin
  • --max-issues was removed — use --fail-on-severity.

  • --analysis-mode light|full was added — see Analysis modes.

  • --api-version was added, alongside the existing --language-version.

  • --classpath is no longer marked experimental, and paths are validated when the CLI starts so misconfigured classpaths fail fast.

  • --report accepts the new report IDs.

The compiler plugin was removed​

The Kotlin compiler plugin is gone in 2.x — both the io.github.detekt.gradle.compiler-plugin Gradle plugin and the io.gitlab.arturbosch.detekt:detekt-compiler-plugin artifact. There is no replacement.

plugins {
- id("io.github.detekt.gradle.compiler-plugin") version "1.23.8"
}

detekt {
- enableCompilerPlugin = true
}

Run detekt through the Gradle plugin or the CLI instead. The analysis tasks cover the same source, and with full analysis they have the same type information the compiler plugin had.


Rule author guide​

If you maintain a custom rule set, custom reports, custom processors, or any other detekt extension, you'll need to update both your dependency coordinates and your code.

Coordinates and imports​

The mechanical part: every detekt package moved under dev.detekt, and detekt no longer uses the Kotlin compiler's shaded IntelliJ classes.

1.x package2.x package
io.gitlab.arturbosch.detekt.apidev.detekt.api
io.gitlab.arturbosch.detekt.api.internaldev.detekt.api (most of it is public now)
io.gitlab.arturbosch.detekt.rules (psi-utils helpers)dev.detekt.psi
io.github.detekt.psidev.detekt.psi
io.github.detekt.toolingdev.detekt.tooling
io.github.detekt.test.utilsdev.detekt.test.utils
org.jetbrains.kotlin.com.intellij.*com.intellij.*
-import io.gitlab.arturbosch.detekt.api.Rule
-import io.gitlab.arturbosch.detekt.rules.isPartOf
-import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import dev.detekt.api.Rule
+import dev.detekt.psi.isPartOf
+import com.intellij.psi.PsiElement

New artifacts were introduced for testing:

  • dev.detekt:detekt-test-assertj — the AssertJ-based assertion helpers (split out of detekt-test)
  • dev.detekt:detekt-test-junit — JUnit 5 environment fixtures (@KotlinCoreEnvironmentTest, etc.)

If you depended on detekt-test, you most likely need to add one or both of these too.

Rule API​

The Rule class signature changed:

-class MyRule(config: Config) : Rule(config) {
- override val issue = Issue(
- javaClass.simpleName,
- Severity.Style,
- "Detects something I dislike.",
- Debt.FIVE_MINS,
- )
-
- override fun visitNamedFunction(function: KtNamedFunction) {
- report(CodeSmell(issue, Entity.from(function), "Don't do this."))
- }
-}
+class MyRule(config: Config) : Rule(
+ config,
+ description = "Detects something I dislike.",
+) {
+ override fun visitNamedFunction(function: KtNamedFunction) {
+ report(Finding(Entity.from(function), "Don't do this."))
+ }
+}

Key points:

  • Rule takes the description as a constructor parameter — move the text out of Issue. An optional third parameter takes a documentation URI.
  • The 1.x Issue class and the issue override are gone. Note that dev.detekt.api.Issue does exist in 2.x, but it is a different concept — see Findings and Issues.
  • Debt is gone — detekt no longer tracks per-issue debt minutes.
  • CodeSmell was renamed to Finding, and its constructor no longer takes an Issue: Finding(entity, message, references, suppressReasons).
  • Severity is configured per rule via YAML config, not via an enum in code. Rule.severity and per-call severity overrides are gone. The enum is now Info, Warning, Error.
  • Rule no longer carries an aliases set; declare aliases with the @Alias annotation.
  • The rule's name comes from Rule.ruleName (a RuleName value class), defaulting to the class name. Override it if you need a different reported name.
  • ThresholdedCodeSmell and ThresholdRule were removed — emit a plain Finding and put the threshold information into the message.

Rules that correct code must implement the dev.detekt.api.AutoCorrectable marker interface. Without it, Rule.autoCorrect is always false. For capable rules, a rule-level autoCorrect value takes precedence over the ruleset-level value.

Entity also changed: name and compact() are gone and ktElement is now non-null.

RuleSetProvider​

RuleSetProvider returns rule factories, not rule instances, and uses the RuleSetId class (this was RuleSet.Id in earlier 2.0 alphas):

class MyRuleSetProvider : RuleSetProvider {
- override val ruleSetId = "MyRuleSet"
+ override val ruleSetId = RuleSetId("MyRuleSet")

- override fun instance(config: Config) = RuleSet(
- ruleSetId,
- listOf(
- MyRule(config),
- ),
- )
+ override fun instance(): RuleSet = RuleSet(
+ ruleSetId,
+ listOf(
+ ::MyRule,
+ ),
+ )
}

This lets detekt construct each rule lazily with its own scoped config, and is the foundation for running the same rule multiple times with different configurations.

Remember to rename the service file under META-INF/services/ from io.gitlab.arturbosch.detekt.api.RuleSetProvider to dev.detekt.api.RuleSetProvider.

Type resolution: Analysis API instead of BindingContext​

This is the biggest change for rule authors. In 1.x, type-aware rules looked like:

@RequiresTypeResolution
class MyRule(config: Config) : Rule(config) {
override fun visitCallExpression(expression: KtCallExpression) {
val descriptor = expression.getResolvedCall(bindingContext)?.resultingDescriptor ?: return
// ...
}
}

In 2.x, you implement the marker interface RequiresAnalysisApi and use the analyze {} block:

class MyRule(config: Config) : Rule(
config,
description = "..."
), RequiresAnalysisApi {
override fun visitCallExpression(expression: KtCallExpression) {
analyze(expression) {
val symbol = expression.resolveToCall()?.successfulFunctionCallOrNull()?.symbol
// ...
}
}
}

@RequiresTypeResolution (the annotation) is gone. BindingContext, ResolvedCall.isCalling, KotlinType.fqNameOrNull, DataFlowValueFactory and all other K1 helpers are removed from the public API.

A rule marked with RequiresAnalysisApi only runs in full analysis mode; in light mode it is skipped.

JetBrains maintains an official K1 → Analysis API migration guide that covers the API mapping in depth. For concrete detekt-flavoured examples, look at the rule migration PRs we landed during the 2.0 alpha cycle — there are over 100 of them and most are 30–50 lines, so finding one that's structurally close to your rule is usually quick. A few good starting points by category:

Thank you to everyone who migrated a rule

Porting every type-aware rule off the K1 compiler was the single largest piece of work in the 2.0 cycle, and most of it was done by people volunteering their time. Our thanks to, in alphabetical order:

@3flex, @atulgpt, @BraisGabin, @brunoescalona, @inorichi, @marschwar, @segunfamisa, @t-kameyama and @travisMiehm.

The migration was tracked per rule set in #8039–#8046.

Findings, Issues and Detektion​

detekt now distinguishes the two halves of what 1.x called a "finding":

  • Finding is what a rule reports: an Entity, a message, optional references and suppress reasons. It knows nothing about the rule that produced it.
  • Issue is what the engine produces after attaching the RuleInstance and the resolved Severity. This is what reports consume.

Detektion changed accordingly, and is now an immutable class rather than an interface:

-detektion.findings // Map<RuleSetId, List<Finding>>
-detektion.add(notification) // mutates in place
-detektion.addData(key, value) / getData(key)
+detektion.issues // List<Issue>
+detektion + notification // returns a new Detektion
+detektion.userData // Map<String, Any>; use `detektion + (key to value)`

Detektion no longer implements UserDataHolder, and Notification.isError was removed.

Custom reports​

If you implemented a custom OutputReport or ConsoleReport:

  • Both are now interfaces with a single render(Detektion): String method.
  • OutputReport.ending, OutputReport.name and OutputReport.write(...) are gone — the engine decides where output goes, so your report can be written to any path and extension.
  • Extension.init(Config) was removed; only init(SetupContext) remains. Extension.id is still required, Extension.priority now has a default.
  • FileProcessListener.onFinish(files, result) returns a Detektion instead of mutating one, and no longer receives a BindingContext.

Testing rules​

The testing API was streamlined:

  • compileAndLint(...) → lint(...). Snippet compilation is now controlled by the compile parameter and the compile-test-snippets system property.
  • compileAndLintWithContext(env, ...) → lintWithContext(env, ...). The environment parameter is a dev.detekt.test.utils.KotlinEnvironmentContainer, not a KotlinCoreEnvironment.
  • lintWithContext only accepts rules that implement RequiresAnalysisApi, and lint rejects them — the compiler enforces the split for you.
  • getContextForPaths(...) is gone along with BindingContext.
  • AssertJ-style assertions moved to a new module: add testImplementation("dev.detekt:detekt-test-assertj:2.0.0"). ThresholdedCodeSmellAssert, FindingsAssert.hasStartSourceLocations, hasEndSourceLocations and hasTextLocations(String) were removed.
  • The JUnit 5 extensions live in dev.detekt:detekt-test-junit: @KotlinCoreEnvironmentTest for tests that need a classpath, and the new @KotlinAnalysisApiEngineTest.
  • TestConfig is constructed directly, e.g. TestConfig("active" to "true").

Configuration annotations​

@Configuration, @ActiveByDefault and @Alias used to live in an internal package. They are now part of the public API:

-import io.gitlab.arturbosch.detekt.api.internal.Configuration
+import dev.detekt.api.Configuration

@RequiresTypeResolution has no equivalent — implement RequiresAnalysisApi instead.

Config itself changed too. parentPath was replaced by parent, subConfigKeys() was added, and lookups are now type-safe: the single abstract member takes the expected type, so a value of the wrong YAML type no longer gets silently coerced.

class MyConfig : Config {
- override fun valueOrNull(key: String): Any? = ...
- override fun valueOrDefault(key: String, default: Any): Any = ...
+ override fun <T : Any> valueOrNull(key: String, type: KClass<T>): T? = ...
}

valueOrDefault(key, default) and the single-argument valueOrNull(key) still exist as reified extension functions, so call sites are unchanged — only custom Config implementations need updating. This is the API side of the stricter YAML parsing users see.

Other removed / renamed extension points​

  • MultiRule is gone — split it into individual Rules.
  • BaseRule, ThresholdRule, LazyRegex, SingleAssign, SplitPattern, CommaSeparatedPattern, Compactable, HasEntity, HasMetrics, Metric, Context, DefaultContext, ConfigAware, AnnotationExcluder, FilePath, UnstableApi, safeAs — all removed.
  • txt report support is removed.
  • LicenseHeaderLoaderExtension is no longer auto-registered as a processor.
  • DetektProgressListener and InvalidConfigurationError are no longer part of the public API.
  • configWithAndroidVariants moved from detekt-api to detekt-rules-ktlint-wrapper.
  • If you embed detekt programmatically through detekt-tooling, MaxIssuesReached was replaced by IssuesFound, FunctionMatcher moved to dev.detekt.psi in detekt-psi-utils, and ProjectSpec gained an analysisMode property.

Gradle plugin extension points​

If you extended the Gradle plugin (custom task type, custom convention plugin):

-import io.gitlab.arturbosch.detekt.Detekt
-import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
-import io.gitlab.arturbosch.detekt.extensions.DetektExtension
-import io.gitlab.arturbosch.detekt.report.ReportMergeTask
+import dev.detekt.gradle.Detekt
+import dev.detekt.gradle.DetektCreateBaselineTask
+import dev.detekt.gradle.extensions.DetektExtension
+import dev.detekt.gradle.report.ReportMergeTask
  • The plugin classes themselves live in dev.detekt.gradle.plugin.*.
  • DetektExtension is an interface of lazy Gradle properties — see The detekt extension is now lazy.
  • The plugin compiles against the Kotlin Gradle plugin API and the AGP API only, so it works with both the standalone Kotlin Gradle Plugin and AGP 9's built-in Kotlin support.

Need help?​

  • Track the 2.0.0 milestone here: github.com/detekt/detekt/milestone/42
  • Browse the alpha changelogs in Changelog 2.0.0 for an exhaustive list of changes per release.
  • If you hit a migration issue that isn't covered here, open an issue and tag it migration — we'll fold the answer back into this page.