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.
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
| Tool | detekt 1.23.x | detekt 2.0.x |
|---|---|---|
| Gradle | 6.8.3 | 8.14 |
| Android Gradle Plugin | 4.x | 8.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 ID | Purpose |
|---|---|
dev.detekt | The main plugin — registers all analysis tasks. Use this one. |
dev.detekt.gradle.base | Only 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 module | 2.x module |
|---|---|
detekt-formatting | detekt-rules-ktlint-wrapper |
detekt-rules-documentation | detekt-rules-comments |
detekt-rules-empty | detekt-rules-empty-blocks |
detekt-rules-errorprone | detekt-rules-potential-bugs |
detekt-report-xml | detekt-report-checkstyle |
detekt-report-md | detekt-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:
| Task | Analysis mode | Notes |
|---|---|---|
detekt | light | Plain task, wired to check. Unchanged. |
detektMain, detektTest, … | full | One per Kotlin compilation. Has a classpath. |
detektMainSourceSet, detektTestSourceSet, … | light | New. 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.
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 id | 2.x report id |
|---|---|
xml | checkstyle |
md | markdown |
-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— includingmaxIssues,excludeCorrectableandweights. SeefailOnSeverity.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 name | 2.x name |
|---|---|
ProjectComplexityProcessor | ProjectCyclomaticComplexityProcessor |
FindingsReport | IssuesReport |
FileBasedFindingsReport | FileBasedIssuesReport |
LiteFindingsReport | LiteIssuesReport |
DetektProgressListener and LicenseHeaderLoaderExtension are no longer processors and must be
removed from the processors list.
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
| Rule | 1.x rule set |
|---|---|
AlsoCouldBeApply | style |
ArrayPrimitive | performance |
CharArrayToStringCall | potential-bugs |
CouldBeSequence | performance |
DontDowncastCollectionTypes | potential-bugs |
DoubleMutabilityForCollection | potential-bugs |
ForEachOnRange | performance |
IteratorHasNextCallsNextMethod | potential-bugs |
IteratorNotThrowingNoSuchElementException | potential-bugs |
MapGetWithNotNullAssertionOperator | potential-bugs |
MissingUseCall | potential-bugs |
MultilineRawStringIndentation | style |
NestedScopeFunctions | complexity |
RedundantHigherOrderMapUsage | style |
ReplaceSafeCallChainWithRun | complexity |
TrimMultilineRawString | style |
UnnecessaryAny | style |
UnnecessaryApply | style |
UnnecessaryFilter | style |
UnnecessaryLet | style |
UnnecessaryReversed | style |
UseAnyOrNoneInsteadOfFind | style |
UseCheckNotNull | style |
UseCheckOrError | style |
UseEmptyCounterpart | style |
UseIfEmptyOrIfBlank | style |
UseIsNullOrEmpty | style |
UseLet | style |
UseOrEmpty | style |
UseRequire | style |
UseRequireNotNull | style |
UseSumOfInsteadOfFlatMapSize | style |
UselessCallOnNotNull | style |
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 rule | 2.x rule |
|---|---|
UnusedImports | UnusedImport |
UnusedPrivateMember | Split into UnusedPrivateFunction and UnusedVariable (UnusedPrivateProperty already existed) |
UnnecessaryAbstractClass | Split into AbstractClassCanBeConcreteClass and AbstractClassCanBeInterface |
CommentOverPrivateFunction | DocumentationOverPrivateFunction |
CommentOverPrivateProperty | DocumentationOverPrivateProperty |
EmptyKtFile | EmptyKotlinFile |
MayBeConst | MayBeConstant |
FunctionMinLength | FunctionNameMinLength |
FunctionMaxLength | FunctionNameMaxLength |
SpacingBetweenPackageAndImports | SpacingAfterPackageAndImports |
UntilInsteadOfRangeTo | RangeUntilInsteadOfRangeTo |
RedundantVisibilityModifierRule | RedundantVisibilityModifier |
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 rule | Replacement |
|---|---|
ComplexMethod | CyclomaticComplexMethod |
MandatoryBracesIfStatements | BracesOnIfStatements with always |
OptionalWhenBraces | BracesOnWhenStatements |
TrailingComma | TrailingCommaOnCallSite / TrailingCommaOnDeclarationSite |
DuplicateCaseInWhenExpression, MissingWhenCase, RedundantElseInWhen | Reported by the Kotlin compiler |
ForbiddenPublicDataClass, LibraryCodeMustSpecifyReturnType, LibraryEntitiesShouldNotBePublic | The 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…:
| Rule | Old key(s) | New key(s) |
|---|---|---|
LongMethod | threshold | allowedLines |
LargeClass | threshold | allowedLines |
LongParameterList | functionThreshold / constructorThreshold | allowedFunctionParameters / allowedConstructorParameters |
CyclomaticComplexMethod | threshold | allowedComplexity |
CognitiveComplexMethod | threshold | allowedComplexity |
ComplexCondition | threshold | allowedConditions |
ComplexInterface | threshold | allowedDefinitions |
NestedBlockDepth | threshold | allowedDepth |
NestedScopeFunctions | threshold | allowedDepth |
MethodOverloading | threshold | allowedOverloads |
NamedArguments | threshold | allowedArguments |
CouldBeSequence | threshold | allowedOperations |
StringLiteralDuplication | threshold / excludeStringsWithLessThan5Characters | allowedDuplications / allowedWithLengthLessThan |
TooManyFunctions | thresholdInFiles, thresholdInClasses, thresholdInInterfaces, thresholdInObjects, thresholdInEnums | allowedFunctionsPerFile, allowedFunctionsPerClass, allowedFunctionsPerInterface, allowedFunctionsPerObject, allowedFunctionsPerEnum |
Other key renames:
| Rule | Old key | New key |
|---|---|---|
ForbiddenImport | imports / forbiddenPatterns | forbiddenImports (plus a new allowedImports) |
AbsentOrWrongFileLicense | licenseTemplateFile | licenseTemplate |
UnderscoresInNumericLiterals | acceptableDecimalLength | acceptableLength |
IgnoredReturnValue | restrictToAnnotatedMethods | restrictToConfig |
ForbiddenComment | values / customMessage | comments (a list of value/reason pairs) |
EmptyFunctionBlock | ignoreOverriddenFunctions | ignoreOverridden |
MemberNameEqualsClassName | ignoreOverriddenFunction | ignoreOverridden |
FunctionOnlyReturningConstant | excludeAnnotatedFunction | ignoreAnnotated |
LateinitUsage | excludeAnnotatedProperties | ignoreAnnotated |
UseDataClass, UnnecessaryAbstractClass | excludeAnnotatedClasses | ignoreAnnotated |
The ignoreOverridden option on BooleanPropertyNaming, ConstructorParameterNaming,
FunctionNaming, FunctionParameterNaming and VariableNaming was ignored in 1.x and is now
removed.
Baselines must be regenerated
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,--pluginsand--classpathaccepted,(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-issueswas removed — use--fail-on-severity. -
--analysis-mode light|fullwas added — see Analysis modes. -
--api-versionwas added, alongside the existing--language-version. -
--classpathis no longer marked experimental, and paths are validated when the CLI starts so misconfigured classpaths fail fast. -
--reportaccepts 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 package | 2.x package |
|---|---|
io.gitlab.arturbosch.detekt.api | dev.detekt.api |
io.gitlab.arturbosch.detekt.api.internal | dev.detekt.api (most of it is public now) |
io.gitlab.arturbosch.detekt.rules (psi-utils helpers) | dev.detekt.psi |
io.github.detekt.psi | dev.detekt.psi |
io.github.detekt.tooling | dev.detekt.tooling |
io.github.detekt.test.utils | dev.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 ofdetekt-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:
Ruletakes thedescriptionas a constructor parameter — move the text out ofIssue. An optional third parameter takes a documentationURI.- The 1.x
Issueclass and theissueoverride are gone. Note thatdev.detekt.api.Issuedoes exist in 2.x, but it is a different concept — see Findings and Issues. Debtis gone — detekt no longer tracks per-issue debt minutes.CodeSmellwas renamed toFinding, and its constructor no longer takes anIssue:Finding(entity, message, references, suppressReasons).Severityis configured per rule via YAML config, not via an enum in code.Rule.severityand per-call severity overrides are gone. The enum is nowInfo,Warning,Error.Ruleno longer carries analiasesset; declare aliases with the@Aliasannotation.- The rule's name comes from
Rule.ruleName(aRuleNamevalue class), defaulting to the class name. Override it if you need a different reported name. ThresholdedCodeSmellandThresholdRulewere removed — emit a plainFindingand 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:
- Style / complexity — #8422, #8408, #8482
- Potential bugs — #8157, #8237, #8246
- Exceptions — #8215, #8216, #8217
- Coroutines — #8259, #8289, #8290
- Naming — #8201, #8202, #8325
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.
Findings, Issues and Detektion
detekt now distinguishes the two halves of what 1.x called a "finding":
Findingis what a rule reports: anEntity, a message, optional references and suppress reasons. It knows nothing about the rule that produced it.Issueis what the engine produces after attaching theRuleInstanceand the resolvedSeverity. 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): Stringmethod. OutputReport.ending,OutputReport.nameandOutputReport.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; onlyinit(SetupContext)remains.Extension.idis still required,Extension.prioritynow has a default.FileProcessListener.onFinish(files, result)returns aDetektioninstead of mutating one, and no longer receives aBindingContext.
Testing rules
The testing API was streamlined:
compileAndLint(...)→lint(...). Snippet compilation is now controlled by thecompileparameter and thecompile-test-snippetssystem property.compileAndLintWithContext(env, ...)→lintWithContext(env, ...). The environment parameter is adev.detekt.test.utils.KotlinEnvironmentContainer, not aKotlinCoreEnvironment.lintWithContextonly accepts rules that implementRequiresAnalysisApi, andlintrejects them — the compiler enforces the split for you.getContextForPaths(...)is gone along withBindingContext.- AssertJ-style assertions moved to a new module: add
testImplementation("dev.detekt:detekt-test-assertj:2.0.0").ThresholdedCodeSmellAssert,FindingsAssert.hasStartSourceLocations,hasEndSourceLocationsandhasTextLocations(String)were removed. - The JUnit 5 extensions live in
dev.detekt:detekt-test-junit:@KotlinCoreEnvironmentTestfor tests that need a classpath, and the new@KotlinAnalysisApiEngineTest. TestConfigis 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
MultiRuleis gone — split it into individualRules.BaseRule,ThresholdRule,LazyRegex,SingleAssign,SplitPattern,CommaSeparatedPattern,Compactable,HasEntity,HasMetrics,Metric,Context,DefaultContext,ConfigAware,AnnotationExcluder,FilePath,UnstableApi,safeAs— all removed.txtreport support is removed.LicenseHeaderLoaderExtensionis no longer auto-registered as a processor.DetektProgressListenerandInvalidConfigurationErrorare no longer part of the public API.configWithAndroidVariantsmoved fromdetekt-apitodetekt-rules-ktlint-wrapper.- If you embed detekt programmatically through
detekt-tooling,MaxIssuesReachedwas replaced byIssuesFound,FunctionMatchermoved todev.detekt.psiindetekt-psi-utils, andProjectSpecgained ananalysisModeproperty.
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.*. DetektExtensionis an interface of lazy Gradle properties — see Thedetektextension 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.