Problem
java-workflows has no dependency-audit job. Every other family has one:
| Family | Audit tool |
|---|
python-workflows | pip-audit |
go-workflows | govulncheck + gosec |
node-workflows | npm audit |
java-workflows | none |
Both lanes go straight from build to sbom. #45 adds sbom_enabled and grype_enabled for parity with the rest of the estate, but deliberately leaves this gap alone — it needs its own decision.
Why it was missed
Almost certainly not an oversight. The three existing audit lanes all wrap a tool shipped by the language's own stewards: PyPA publishes pip-audit, the Go team publishes govulncheck, npm builds audit into the CLI. Maven and Gradle have no equivalent — there is no mvn audit goal. There was no obvious thing to reach for, so nothing was reached for.
Evaluation
I looked at what could fill the slot and, importantly, whether anything would add value beyond the Grype-on-SBOM lane Java already has.
| Tool | Database | Overlap with existing Grype lane | Key required? | Runs without editing the project? | Gradle without a lockfile |
|---|
| OWASP Dependency-Check | NVD (CPE) + CISA KEV | Low | Effectively yes | Maven ✅ / CLI ✅ / Gradle ❌ | ✅ works |
| Google OSV-Scanner | OSV (≈GHSA for Maven) | Very high | No | ✅ | ❌ scans nothing |
Sonatype OSS Index / nancy | Sonatype | n/a — nancy is Go-only | Mandatory since 09/2025 | — | — |
| Dependabot / dependency-review | GHSA | 100% | GHAS for private repos | ✅ | ❌ needs submission |
| Trivy | GHSA (Maven) | 100% | No | ✅ | ❌ |
Three findings worth recording:
1. Nothing does reachability analysis for Java. There is no govulncheck equivalent — no OSS tool does bytecode call-graph analysis with framework-aware entry points. The strongest possible value-add simply is not available. Do not size expectations against the Go lane.
2. nancy does not support Java at all (go list -json -deps only), and the OSS Index route has closed regardless: Sonatype began enforcing API tokens in September 2025 and is migrating to Sonatype Guide, with Dependency-Check now silently disabling the analyser when credentials are absent. Strike this candidate.
3. Four of the five candidates just re-query GitHub Security Advisories — the same source Grype already uses. Adding one buys a second opinion on identical data plus a second stream of duplicate findings to triage.
A correction worth recording
Initial research suggested Grype's Java lane is GHSA-only because CPE matching is disabled for Java — implying Java is uniquely disadvantaged. Verified against cmd/grype/cli/options/match.go upstream, and the framing is wrong:
Java: dontUseCpe,
Dotnet: dontUseCpe,
Golang: defaultGolangConfig(), // UseCPEs: falseJavascript: dontUseCpe,
Python: dontUseCpe,
Ruby: dontUseCpe,
Rust: dontUseCpe,
Hex: dontUseCpe,
Stock: useCpe,
JVM: useCpe,
Every language ecosystem is purl/GHSA-matched by default, not just Java. Only Stock (unknown package types) and JVM (the JDK itself, not libraries) use CPEs. This is a deliberate Anchore precision-over-recall decision applied estate-wide, so it is not an argument for treating Java specially.
Recommended sequence
Step 1 — verify the SBOMs actually contain anything. This matters more than the tool choice. Grype-on-SBOM is only as good as the SBOM, and if Syft runs against a source tree that has not been built, a Java project yields close to nothing: no JARs on disk, and Syft's pom.xml cataloging does not resolve the transitive graph the way Maven does. A pipeline scanning a near-empty SBOM passes forever, in green. Check the component_count on a few real Maven and Gradle consumers before anything else. If it is low, the gap is in SBOM generation, not the missing audit lane, and no amount of extra scanning will fix it.
Step 2 — measure the CPE delta in the tool we already run. Set match.java.using-cpes: true for the Java lane on a canary repo and diff the findings. That turns on precisely the NVD/CPE axis Dependency-Check's value proposition rests on, using a scanner, cache and report format already integrated. If the extra findings are mostly CPE mismatch noise, revert — GHSA-only was a defensible choice and Anchore made it deliberately. If real NVD-only CVEs appear, the gap is quantified with evidence.
Step 3 — only then consider Dependency-Check. It is the sole candidate with genuinely different data, and the only one that works on an unlocked Gradle project. It is also by far the most expensive: 20+ minute cold NVD database build, an API key needed in practice (the CLI's --nvdApiDelay defaults to 8000 ms without one versus 3500 ms with), documented 403 rate-limit problems in CI requiring a caching strategy, and CPE-based matching with the industry's highest false-positive rate. If adopted, use the CLI against built artifacts — mvn org.owasp:dependency-check-maven:check also works without a pom.xml entry, but the Gradle plugin requires editing build.gradle, which our workflows must not do to consumer projects.
Recommendation
Do not add a fifth SCA tool on general principle. Do steps 1 and 2 first, and let measurement decide whether step 3 is justified. The honest answer may well be that Java needs better SBOMs rather than another scanner.
On Gradle specifically, be realistic: dependency locking is opt-in and most projects do not use it, so OSV-Scanner, Trivy and dependency-review all degrade to silent no-ops there. Any decision needs to state explicitly what unlocked Gradle projects get.
Unverified
- The claim that Grype's Java matching yields zero NVD-sourced findings is reasoned from the config default and NVD's CPE-only join key; it was not confirmed by running Grype against a real Java SBOM. Step 2 confirms it empirically.
- Injecting the Dependency-Check Gradle plugin via
gradle --init-script (avoiding a build.gradle edit) is a standard Gradle mechanism but is not documented by the Dependency-Check project. It would need prototyping before being committed to a reusable workflow.
Problem
java-workflowshas no dependency-audit job. Every other family has one:python-workflowspip-auditgo-workflowsgovulncheck+gosecnode-workflowsnpm auditjava-workflowsBoth lanes go straight from
buildtosbom. #45 addssbom_enabledandgrype_enabledfor parity with the rest of the estate, but deliberately leaves this gap alone — it needs its own decision.Why it was missed
Almost certainly not an oversight. The three existing audit lanes all wrap a tool shipped by the language's own stewards: PyPA publishes
pip-audit, the Go team publishesgovulncheck, npm buildsauditinto the CLI. Maven and Gradle have no equivalent — there is nomvn auditgoal. There was no obvious thing to reach for, so nothing was reached for.Evaluation
I looked at what could fill the slot and, importantly, whether anything would add value beyond the Grype-on-SBOM lane Java already has.
nancynancyis Go-onlyThree findings worth recording:
1. Nothing does reachability analysis for Java. There is no
govulncheckequivalent — no OSS tool does bytecode call-graph analysis with framework-aware entry points. The strongest possible value-add simply is not available. Do not size expectations against the Go lane.2.
nancydoes not support Java at all (go list -json -depsonly), and the OSS Index route has closed regardless: Sonatype began enforcing API tokens in September 2025 and is migrating to Sonatype Guide, with Dependency-Check now silently disabling the analyser when credentials are absent. Strike this candidate.3. Four of the five candidates just re-query GitHub Security Advisories — the same source Grype already uses. Adding one buys a second opinion on identical data plus a second stream of duplicate findings to triage.
A correction worth recording
Initial research suggested Grype's Java lane is GHSA-only because CPE matching is disabled for Java — implying Java is uniquely disadvantaged. Verified against
cmd/grype/cli/options/match.goupstream, and the framing is wrong:Every language ecosystem is purl/GHSA-matched by default, not just Java. Only
Stock(unknown package types) andJVM(the JDK itself, not libraries) use CPEs. This is a deliberate Anchore precision-over-recall decision applied estate-wide, so it is not an argument for treating Java specially.Recommended sequence
Step 1 — verify the SBOMs actually contain anything. This matters more than the tool choice. Grype-on-SBOM is only as good as the SBOM, and if Syft runs against a source tree that has not been built, a Java project yields close to nothing: no JARs on disk, and Syft's
pom.xmlcataloging does not resolve the transitive graph the way Maven does. A pipeline scanning a near-empty SBOM passes forever, in green. Check thecomponent_counton a few real Maven and Gradle consumers before anything else. If it is low, the gap is in SBOM generation, not the missing audit lane, and no amount of extra scanning will fix it.Step 2 — measure the CPE delta in the tool we already run. Set
match.java.using-cpes: truefor the Java lane on a canary repo and diff the findings. That turns on precisely the NVD/CPE axis Dependency-Check's value proposition rests on, using a scanner, cache and report format already integrated. If the extra findings are mostly CPE mismatch noise, revert — GHSA-only was a defensible choice and Anchore made it deliberately. If real NVD-only CVEs appear, the gap is quantified with evidence.Step 3 — only then consider Dependency-Check. It is the sole candidate with genuinely different data, and the only one that works on an unlocked Gradle project. It is also by far the most expensive: 20+ minute cold NVD database build, an API key needed in practice (the CLI's
--nvdApiDelaydefaults to 8000 ms without one versus 3500 ms with), documented 403 rate-limit problems in CI requiring a caching strategy, and CPE-based matching with the industry's highest false-positive rate. If adopted, use the CLI against built artifacts —mvn org.owasp:dependency-check-maven:checkalso works without apom.xmlentry, but the Gradle plugin requires editingbuild.gradle, which our workflows must not do to consumer projects.Recommendation
Do not add a fifth SCA tool on general principle. Do steps 1 and 2 first, and let measurement decide whether step 3 is justified. The honest answer may well be that Java needs better SBOMs rather than another scanner.
On Gradle specifically, be realistic: dependency locking is opt-in and most projects do not use it, so OSV-Scanner, Trivy and dependency-review all degrade to silent no-ops there. Any decision needs to state explicitly what unlocked Gradle projects get.
Unverified
gradle --init-script(avoiding abuild.gradleedit) is a standard Gradle mechanism but is not documented by the Dependency-Check project. It would need prototyping before being committed to a reusable workflow.