Turn on Gradle dependency locking in the template build script, commit the generated lock files, and consider the STRICT lock mode. By default a build fails when resolution does not match the lock, and STRICT also fails when a locked configuration has no lock state at all. Update entries on purpose with a flag, not by accident.
The problem it solves
Gradle's documentation points out that dynamic versions such as 1.+ or a version range can break builds unexpectedly, because the exact version that resolves can change over time. Locking records the resolved result so a build uses the same versions until you decide otherwise.
For a platform team, this gives the scanner a stable inventory and gives reviewers a diff when versions change.
What the docs describe
- You can activate locking for specific configurations or for all configurations.
- Lock state is generated and updated through a Gradle run, and lock files live in a defined location with a defined structure.
- A lock mode setting fine tunes behavior. The docs describe default, strict and lenient modes, and show STRICT, set in the build script as lockMode equals LockMode.STRICT inside dependencyLocking.
- You can update only specific modules with the --update-locks flag, which takes a comma separated list of module notations.
- You can ignore specific dependencies from lock state if you must.
- The documentation also lists limitations, so read that section before you roll out.
Template checklist
| Step | Detail |
|---|---|
| Enable locking | In the shared convention plugin or build script, not per project copy |
| Choose mode | STRICT in CI so a locked configuration with no lock state fails the build |
| Generate and commit | Run the lock generation task in the template CI and commit the files |
| Make updates explicit | Document the one command to refresh locks |
| Review lock diffs | Treat a lock change as a code change that needs a reviewer |
| Scan | Run dependency scanning against the locked result |
If you use a convention plugin or shared build logic, put locking there so every service inherits it. That follows the same idea as a shared pipeline template; see version and roll out changes to a shared pipeline template.
Rollout concerns
Enabling STRICT in an existing project can fail builds that relied on dynamic versions. For old services, start with the default mode, generate locks, and move to STRICT once teams have cleaned up dynamic versions.
Large multi module builds produce many lock files. Decide early whether you want per project locks or a combined approach, and tell reviewers how to read the diffs.
Locks and update automation
A lock file stops change, and update automation reintroduces it deliberately. Run both. Update pull requests regenerate locks, tests run, the scan gate runs, then a human or a trusted rule merges.
Do you need locks before your first scan?
Not strictly; see whether you need Gradle lockfiles before the first scan. But for a template, locks are a cheap default.
Where Safeguard fits
Safeguard builds SBOMs and scans dependencies for Java projects, applies policies and gates to pull requests, and uses reachability analysis to rank findings. Try a free scan at gold.safeguard.sh/scan.
Try it yourself
Open the inventory and see how many indirect packages a build resolves, then think about how many of those a lock file records.
Click through it at your own pace, or open it full screen.
Quick check
What does STRICT lock mode do?
Per Gradle's docs, in addition to the default validations, it fails if a configuration marked as locked has no lock state associated with it.
How do I update one dependency in the lock?
Use the --update-locks flag with the module notation, as the Gradle documentation describes.
Sources
Last reviewed: 19 October 2026.