The Detekt (a Kotlin static analysis tool) integration for the Bazel build system.
- configuration and baseline files;
- HTML, text, XML, Markdown, and SARIF reports;
- plugins;
- customizable Detekt version;
- persistent workers support;
- baseline generation via
detekt_create_baseline; - configuration options via attributes.
bazel_rules_detekt |
Default Detekt | Kotlin compiler | Max language_version |
Min JDK | Max tested JDK | Bazel |
|---|---|---|---|---|---|---|
| v0.8.1.9 – v0.8.2.3 | 1.23.8 | 2.0.21 | 2.0 |
8 | 21 | 8.x – 9.x |
| v0.8.1.3 – v0.8.1.8 | 1.23.5 | 1.9.22 | 1.9 |
8 | 17 | 7.x – 9.x |
| v0.8.1 – v0.8.1.2 | 1.23.1 | 1.9.0 | 1.9 |
8 | 17 | 6.x |
| v0.7.0 | 1.22.0 | — | — | 8 | — | 5.x |
| v0.6.0 – v0.6.1 | 1.19.0 – 1.21.0 | — | — | 8 | — | 5.x |
| v0.4.0 – v0.5.0 | 1.15.0 | — | — | — | — | 4.x |
| v0.3.0 | 1.7.4 | — | — | — | — | 3.x |
| v0.1.0 – v0.2.0 | 1.2.0 | — | — | — | — | 1.x |
For detailed per-Detekt-version Kotlin and JDK compatibility, see the Detekt compatibility table.
Note: The Kotlin compiler bundled with Detekt determines which
language_versionvalues are valid — setting it higher than what the bundled compiler supports will cause Detekt to fail. The default Detekt version can always be overridden — see Detekt Version.
Note: Detekt 2.x is currently in alpha. The default is 2.0.0-alpha.6; the compatibility suite also validates a Detekt 1.23.8 override.
Note: JDK 25 and above are not supported with Detekt 1.23.x. The bundled Kotlin compiler performs a hard version check that fails on JDK 25+. This is resolved in the Detekt 2.x series.
Note: The wrapper reflects over the Detekt 1.x and 2.x CLI entry points during this alpha migration. Detekt 1.x compatibility will be deprecated after Detekt 2.x stabilizes and removed in a subsequent breaking release.
The project is developed and tested against Bazel 8 and 9 with Bzlmod.
Refer to GitHub releases for the version and the SHA-256 hashsum.
bazel_dep(name = "rules_detekt", version = "...")Once declared in the MODULE.bazel file, the rules can be loaded in the BUILD file.
detekt is a regular Bazel build rule. When Detekt finds violations, the build action itself
fails, stopping bazel build immediately with an error. This makes it behave like a compiler
error — violations block the build.
load("@rules_detekt//detekt:defs.bzl", "detekt")
detekt(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
)$ bazel build //mypackage:my_detektdetekt_test is a Bazel test rule. The build action always succeeds (even when violations are
found), and Bazel then runs a test script that reads the real Detekt exit code and prints findings
to the test output. Violations cause the test to fail rather than the build action.
load("@rules_detekt//detekt:defs.bzl", "detekt_test")
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
)$ bazel test //mypackage:my_detektBecause it is a test target, it is included in bazel test //... alongside your unit tests,
and it supports standard Bazel test flags such as --test_output=all.
detekt |
detekt_test |
|
|---|---|---|
| Bazel rule type | build rule | test rule |
| Run with | bazel build |
bazel test |
| Included in | bazel build //... |
bazel test //... |
| Violation behaviour | build action fails | test fails; build action always passes |
| Text report | printed when action fails | printed to test output when test fails |
| Result caching | yes | yes |
| Bazel test flags | n/a | yes (--test_output, etc.) |
Use detekt when you want violations to block builds the same way a compiler error does. Use
detekt_test when you want Detekt to run alongside your test suite and report results through
the test framework.
detekt_create_baseline is an executable rule that generates or updates a
Detekt baseline file. The baseline suppresses
existing findings so that only new violations fail the build going forward.
load("@rules_detekt//detekt:defs.bzl", "detekt_create_baseline")
detekt_create_baseline(
name = "my_detekt_baseline",
srcs = glob(["src/main/kotlin/**/*.kt"]),
baseline = "detekt/baseline.xml", # path where the baseline will be written
)$ bazel run //mypackage:my_detekt_baselineRunning this target writes the baseline XML file into your source tree under
$BUILD_WORKING_DIRECTORY. If baseline is not specified, the output path defaults to
{package}/default_baseline.xml.
Once the baseline file exists, reference it in your analysis target:
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
baseline = "detekt-baseline.xml",
)All three rules share the same configuration options. In addition to srcs, most attributes correspond directly to
Detekt CLI flags and pass them through when explicitly set.
cfgs, plugins, build_upon_default_config, disable_default_rulesets, jvm_target, language_version,
max_issues, fail_on_severity, and parallel are configured on reusable detekt_config targets.
The executable wrapper and a default_config reference are configured on detekt_toolchain.
srcs, deps, baseline, excludes, includes, all_rules, auto_correct, base_path, config_resource,
is_android, and report options remain rule-level settings.
max_issues is the failure policy for Detekt 1.x. Detekt 2.x uses fail_on_severity (Never, Info, Warning, or
Error). Both policies cannot be active on the same config; inactive values such as max_issues = -1 may
coexist with the other policy. Choose the active option that matches the selected Detekt major version.
See the rule attributes, config attributes, and toolchain attributes.
A plain-text report ({name}_detekt_report.txt) is always generated. Detekt 1.x writes its native text
report; Detekt 2.x writes captured console output to the same artifact. xml_report keeps the same output
name and maps to Detekt 2.x's Checkstyle report ID. Other formats are opt-in.
The default bundled version is 2.0.0-alpha.6. To override it (including the supported Detekt 1.23.8 compatibility runtime):
detekt = use_extension("@rules_detekt//detekt:extensions.bzl", "detekt")
detekt.detekt_version(
version = "...",
sha256 = "...",
)
use_repo(detekt, "detekt_cli_all")To download Detekt from a custom location (e.g. an internal mirror), use the url_templates parameter:
detekt = use_extension("@rules_detekt//detekt:extensions.bzl", "detekt")
detekt.detekt_version(
version = "...",
sha256 = "...",
url_templates = [
"https://my-mirror.example.com/detekt/detekt-cli-{version}-all.jar",
],
)
use_repo(detekt, "detekt_cli_all")To download Detekt from a custom location (e.g., an internal mirror), use the url_templates parameter:
detekt = use_extension("@rules_detekt//detekt:extensions.bzl", "detekt")
detekt.detekt_version(
version = "...",
sha256 = "...",
url_templates = [
"https://my-mirror.example.com/detekt/detekt-cli-{version}-all.jar",
],
)
use_repo(detekt, "detekt_cli_all")Each template may contain {version} which will be replaced with the version string.
detekt_config defines a reusable analysis profile. Multiple profiles can coexist and share the same executable
toolchain; they do not need to be registered. Select a profile with the rule's config attribute:
load("@rules_detekt//detekt:config.bzl", "detekt_config")
load("@rules_detekt//detekt:defs.bzl", "detekt_test")
detekt_config(
name = "strict",
cfgs = ["detekt.yml"],
build_upon_default_config = True,
fail_on_severity = "Error",
jvm_target = "11",
)
detekt_config(
name = "lenient",
fail_on_severity = "Never",
)
detekt_test(
name = "strict_analysis",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":strict",
)
detekt_test(
name = "legacy_analysis",
srcs = glob(["legacy/**/*.kt"]),
config = ":lenient",
)An explicit config replaces the entire default profile, including empty lists, false booleans, and ordinary
attribute defaults. No individual options are inherited or merged. If config is omitted or resolves to None
in a select(), the selected toolchain's default_config is used. The bundled default config preserves the
existing defaults, including JVM target 1.8.
Configuration labels, YAML inputs, and plugin JARs use the target configuration, so their select() expressions
describe the code being analyzed. The wrapper is built in the execution configuration. Plugin JARs are supplied
artifacts; callers are responsible for making them compatible with the runtime and execution platform.
detekt_toolchain selects the executable wrapper and can reference a repository-wide default profile:
load("@rules_detekt//detekt:toolchain.bzl", "detekt_toolchain")
detekt_toolchain(
name = "detekt_default_impl",
default_config = ":strict",
)
toolchain(
name = "detekt_registered",
toolchain = ":detekt_default_impl",
toolchain_type = "@rules_detekt//detekt:toolchain_type",
)Register it in MODULE.bazel:
register_toolchains("//:detekt_registered")Targets without an explicit detekt_toolchain use Bazel's registered toolchain resolution. Register one intended
default; if multiple compatible toolchains are registered, Bazel's registration priority/order decides which wins,
not their names. A target can still select an implementation directly with
detekt_toolchain = ":detekt_default_impl"; this is the implementation label, not the native toolchain()
registration wrapper. An explicit config takes precedence over either selected toolchain's default.
The wrapper is an exec-configured java_binary. Configure its JVM options through Bazel, for example in .bazelrc:
build --host_jvmopt=-Xmx512m
Profiles configure the selected runtime; they do not select a Detekt version. To use the supported Detekt 1.23.8
override, configure that version separately and use its matching 1.x plugins and max_issues policy.
cfgs, plugins, build_upon_default_config, disable_default_rulesets, jvm_target, language_version,
max_issues, fail_on_severity, and parallel belong on detekt_config, not on detekt_toolchain,
detekt, detekt_test, or detekt_create_baseline.
Move these options into a detekt_config target and pass its label using the rule's config attribute.
If migrating a custom toolchain, leave detekt_wrapper on the toolchain and point its default_config
at the new profile. Define separate profiles when targets need different settings; there is no per-option override.
The selected runtime determines which failure policy is valid: use max_issues with Detekt 1.23.8 and
fail_on_severity with Detekt 2.0.0-alpha.6. JVM and Kotlin language versions are also validated by the selected
runtime, and plugins must be built for the same Detekt major version.
The config's plugins attribute accepts .jar file labels, including generated outputs, and targets or
filegroups that contain only JAR files. JavaInfo is not required, and transitive dependencies are not
automatically collected. Non-JAR files are rejected.
Supply ready-to-load plugin JARs containing their required dependencies and META-INF/services entries.
A deploy JAR is useful for plugins with additional dependencies; exclude dependencies already supplied by
Detekt, including its API. Callers must ensure the JAR's bytecode, Detekt API version, and any native contents
are compatible with the selected runtime and execution platform. Generated JARs are built in the target
configuration, with no execution transition applied by detekt_config.
Maven artifact (e.g., the formatting rule set):
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
artifacts = [
"dev.detekt:detekt-rules-ktlint-wrapper:2.0.0-alpha.6",
],
)
use_repo(maven, "maven")load("@rules_detekt//detekt:config.bzl", "detekt_config")
load("@rules_detekt//detekt:defs.bzl", "detekt_test")
detekt_config(
name = "my_detekt_config",
plugins = ["@maven//:dev_detekt_detekt_rules_ktlint_wrapper"],
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":my_detekt_config",
)For a Detekt 1.x override, use the matching legacy coordinate instead:
io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8. Plugin binaries are not interchangeable
between majors because their public API packages differ (io.gitlab.arturbosch.detekt.api versus
dev.detekt.api).
Custom local plugin built with rules_kotlin:
load("@rules_detekt//detekt:config.bzl", "detekt_config")
load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("@rules_detekt//detekt:defs.bzl", "detekt_test")
kt_jvm_library(
name = "my_custom_rules",
srcs = glob(["src/main/kotlin/**/*.kt"]),
resources = glob(["src/main/resources/**"]),
deps = ["@detekt_cli_all//jar"],
)
java_binary(
name = "my_custom_rules_plugin",
create_executable = False,
deploy_env = ["@rules_detekt//detekt/wrapper:bin"],
runtime_deps = [":my_custom_rules"],
)
detekt_config(
name = "my_detekt_config",
plugins = [":my_custom_rules_plugin_deploy.jar"],
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":my_detekt_config",
)The deploy_env excludes dependencies supplied by the bundled Detekt wrapper. If a plugin needs no
additional runtime dependencies, its ordinary JAR (for example :my_custom_rules.jar) is sufficient.
Existing JavaInfo targets can still be used if their default outputs contain only the required JARs.
Pass one or more Detekt YAML configuration files via the config's cfgs attribute. Files must use the .yml extension.
You may pass raw file labels or filegroup targets:
load("@rules_detekt//detekt:config.bzl", "detekt_config")
detekt_config(
name = "my_detekt_config",
cfgs = [":detekt.yml"],
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":my_detekt_config",
)To extend Detekt's built-in defaults rather than replace them, set build_upon_default_config = True on the config:
load("@rules_detekt//detekt:config.bzl", "detekt_config")
detekt_config(
...
build_upon_default_config = True,
...
)Use jvm_target to set the JVM bytecode target version that matches what was used during compilation.
Set it on the config (default: 1.8):
load("@rules_detekt//detekt:config.bzl", "detekt_config")
detekt_config(
name = "my_detekt_config",
jvm_target = "11",
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":my_detekt_config",
)Detekt will report errors for any language features introduced after the specified version if
language_version is specified on the config:
load("@rules_detekt//detekt:config.bzl", "detekt_config")
detekt_config(
name = "my_detekt_config",
language_version = "2.0",
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
config = ":my_detekt_config",
)Type resolution enables more advanced static analysis by giving Detekt access to the full compilation classpath,
including return types, nullability, and symbol information — capabilities that match those of the Kotlin compiler
itself. Rules requiring it are annotated with @RequiresFullAnalysis in Detekt's source.
Provide the compile dependencies through rule-level deps and set config jvm_target and language_version to match the
compilation settings of your project. With Detekt 2.x, a non-empty deps automatically selects full analysis;
the Detekt 1.x runtime keeps its existing classpath behavior.
load("@rules_detekt//detekt:config.bzl", "detekt_config")
detekt_config(
name = "my_detekt_config",
jvm_target = "11",
language_version = "2.0",
)
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
deps = [":my_compiled_library"],
config = ":my_detekt_config",
)By default, Detekt generates a text report internally (used for console output). To export reports as build outputs, enable them explicitly:
detekt_test(
name = "my_detekt",
srcs = glob(["src/main/kotlin/**/*.kt"]),
txt_report = True, # {target_name}_detekt_report.txt
html_report = True, # {target_name}_detekt_report.html
xml_report = True, # {target_name}_detekt_report.xml (Checkstyle format, compatible with SonarQube)
md_report = True, # {target_name}_detekt_report.md
sarif_report = True, # {target_name}_detekt_report.sarif
)Any combination of reports may be enabled.