Enforcing Zero New Issues in a Java/Kotlin CI Pipeline with Qodana Baselines
Legacy Java/Kotlin projects have many existing inspection violations. By generating a Qodana baseline and committing it, CI can fail only on new issues, keeping the build green while enforcing quality.
22 Jul 2025, 14:52 UTC

Problem: Legacy Issues Block Modern CI
Most long‑running Java/Kotlin projects already contain dozens of code‑quality violations detected by IntelliJ inspections. A fresh CI pipeline that fails on any violation would immediately block all commits, making it impossible to adopt the tool without a massive refactor. The goal is to keep the legacy issues hidden while still ensuring that new code does not introduce any new problems.
Desired Outcome
Run Qodana in a CI job so that the build passes when the code has no new inspections, but fails when a new violation appears. Existing violations should be suppressed by a .qodana.baseline file that is committed to the repository.
Prerequisites
- Docker installed on the CI runner.
- Project source code checked into Git (Java/Kotlin).
- Commit the
.qodanadirectory containing the baseline file. - Optional: Qodana Cloud account if you want to use the cloud service; not required for the local Docker approach.
- CI runner with at least 4 GB RAM and 2 CPU cores for the JVM image.
Procedure
1. Generate an Initial Baseline
Run Qodana locally or in a temporary CI job to collect all existing issues. The baseline file will be written to .qodana/.qodana.baseline inside the repository.
# From the project root
mkdir -p .qodana
# Use the JVM image – adjust the tag to the desired Qodana release
# The --output flag writes reports to the .qodana directory
# The --baseline flag tells Qodana to create a baseline file
# The --fail-on-new flag is omitted because we are generating the baseline
docker run \
--rm \
-v $(pwd):/project \
-v $(pwd)/.qodana:/qodana \
-w /project \
jetbrains/qodana-jvm:2026.3 \
qodana run \
--output /qodana \
--baseline /qodana/.qodana.baseline
After the run, inspect .qodana/.qodana.baseline to confirm it contains entries. Commit the file to Git.
2. Configure the CI Workflow
Below is a minimal GitHub Actions workflow that mounts the source, uses the baseline, and fails on any new issue. The same pattern applies to GitLab CI or TeamCity with minor syntax changes.
name: Qodana Check
on:
pull_request:
push:
branches: [ main ]
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Qodana
run: |
docker run \
--rm \
-v ${{ github.workspace }}:/project \
-v ${{ github.workspace }}/.qodana:/qodana \
-w /project \
jetbrains/qodana-jvm:2026.3 \
qodana run \
--output /qodana \
--baseline /qodana/.qodana.baseline \
--fail-on-new
env:
# If using Qodana Cloud you would set QODANA_CLOUD_TOKEN here
- name: Upload Qodana report
uses: actions/upload-artifact@v4
with:
name: qodana-report
path: .qodana
Key points:
--baselinepoints to the committed baseline file.--fail-on-newmakes the container exit with a non‑zero status if any new violation is found.- The report artifacts can be examined later for details.
3. Verify the Setup
To confirm the baseline is working:
- Run the CI job – it should pass, as no new issues exist.
- Create a small code change that violates a chosen inspection (for example, an unused variable).
- Commit and push – the job should now fail, indicating a new issue.
- Inspect the generated SARIF or HTML report to see the exact violation.
4. Recovering from Baseline Drift
Baselines can become stale if files are moved, renamed, or if the inspection configuration changes. When you notice that many old issues reappear as new, consider:
- Regenerate the baseline: Run the baseline generation step again and commit the new file. This will reset the suppression list.
- Adjust the inspection profile: If you intentionally want to tighten the rules, modify the
qodana.ymlprofile and regenerate the baseline after ensuring no critical violations are hidden. - Exclude specific files or directories: Add
exclude_patternstoqodana.ymlso that certain legacy modules are ignored by Qodana.
Common Pitfalls & Tips
- Wrong Docker image: Using the
frontendorphpimage will skip JVM inspections. Always usejetbrains/qodana-jvmfor Java/Kotlin projects. - Memory limits: The JVM image can consume up to 4 GB. If the CI runner is constrained, set
--memory=4gon the Docker run command. - Baseline matching: Qodana matches issues by inspection ID and file path. Moving a file out of
src/main/javacan cause its baseline entries to lose match and reappear as new. - Version drift: The baseline format can change between Qodana releases. Verify the version printed at container start and update the CI script accordingly.
- Large monorepos: Analysis time scales with code size. Consider caching the
.qodanadirectory between jobs if your CI supports it.
Conclusion
By generating a baseline once and committing it to the repository, you can turn Qodana into a gate that only flags new code‑quality problems. The approach scales to large Java/Kotlin codebases, integrates cleanly with existing CI workflows, and provides clear diagnostics when the baseline becomes stale. Use the recovery steps above to keep the baseline relevant without compromising the zero‑new‑issues guarantee.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.