The only durable fix for Log4Shell is to remove the vulnerable code path, and the cleanest way to do that is to upgrade Log4j to a patched release. This guide walks defenders through patching properly: choosing the right target version, finding and replacing every copy including transitive and bundled ones, verifying the result, and making sure the fix does not silently regress on the next deploy. It assumes you have already inventoried your estate and confirmed exposure; if not, start with the Log4Shell scanner to see where the work is needed.
Choose the Right Target Version
Do not aim for the first version that fixed the original flaw. The patch story had several rounds, and the recommended endpoints depend on your Java runtime:
- Java 8 and above: upgrade to Log4j 2.17.1 or later.
- Java 7: use the 2.12.4 backport.
- Java 6: use the 2.3.2 backport.
Versions 2.15.0 and 2.16.0 are improvements but not safe endpoints, because they remain affected by later issues. The reasoning behind these boundaries is covered in is my Log4j version vulnerable and the full sequence in the Log4j patch timeline. Aim straight for 2.17.1 or the relevant backport so you do not have to patch twice.
The Core Upgrade Procedure
For applications you build yourself, the upgrade is usually a dependency change, but the details determine whether it actually takes effect:
- Locate every Log4j artifact. Search for
log4j-coreacross source, build files, and packaged archives, including nested and shaded copies. - Pin the version. Update your build configuration to force
log4j-core(and the matchinglog4j-api) to the target version, overriding whatever a transitive dependency requests. - Resolve the dependency tree. Re-run your build's dependency resolution and confirm no path still pulls an old version.
- Rebuild and repackage. Produce fresh artifacts so the patched JAR is the one that actually ships.
- Redeploy. Roll the new build out to every environment, not just production, so staging cannot reintroduce the old code.
- Verify. Confirm the running version with the version checker and re-scan with the Log4Shell scanner.
Handling Transitive Dependencies
The most common patching mistake is fixing the obvious Log4j reference while a transitive dependency quietly brings in a vulnerable version. Your build tool's dependency-management section is the right place to enforce a single version across the whole graph. Pin both log4j-core and log4j-api so a library that asks for an old release is forced up. After pinning, re-render the dependency tree and read it carefully; the goal is zero remaining references to anything below your target version. It is worth re-running the dependency report after every change, because adding or upgrading one unrelated library can quietly pull a fresh, vulnerable Log4j back into the graph. Treat the dependency tree as something to re-verify, not a one-time check.
Patching Vendor and Bundled Software
You cannot rebuild software you do not own. For appliances, agents, and commercial products that embed Log4j, patching means applying the vendor's update. Track each affected product, find its Log4Shell advisory, and apply the release the vendor identifies as fixed. Where a vendor update lags, apply a temporary mitigation from mitigation without upgrading and keep the item open until the proper patch ships. Use the scanner to confirm the behaviour is gone after the vendor update, since you often cannot read the embedded version directly.
Verifying the Fix Actually Landed
Patching is not done when the build succeeds; it is done when you have proven the vulnerable path is gone. Verification has two halves:
- Version verification. Confirm every running instance reports a safe version with the version checker, including any second or third bundled copy.
- Behaviour verification. Re-run the Log4Shell scanner against the host so you confirm the live service no longer exhibits the vulnerable lookup behaviour.
Doing both matters because a host can report a patched primary version while still shipping a forgotten old JAR in a plugin or sidecar.
Preventing Regressions
A frustrating reality is that patched systems can become vulnerable again. A redeploy from an old artifact, a restored backup, a newly added dependency, or a container image built from a stale base layer can all reintroduce vulnerable Log4j. To stop this:
- Add a build-time guard. Fail the build if any resolved Log4j version is below your target.
- Rebuild base images. Ensure container base layers and golden images carry only patched Log4j.
- Scan continuously. Schedule periodic scans so a regression is caught quickly, not months later.
- Keep standing detections. Maintain the log and network detections from detecting Log4Shell in logs so attempts are still visible.
Do Not Skip the Log Hunt
Patching closes the door going forward, but it tells you nothing about whether someone walked through it earlier. Before you consider the incident closed, hunt your historical logs for JNDI lookup patterns on the hosts that were vulnerable, and check network telemetry for outbound connections that would indicate a successful attempt. The methodology is in detecting Log4Shell in logs, and prioritisation across a large estate is covered in triaging Log4Shell with a scanner.
A Sensible Order of Operations
With limited time, sequence the work by exposure. Patch internet-facing and high-value systems first, because those are under active probing. Then move to internal systems that process untrusted input, since attacker data can reach them indirectly. Finally address isolated internal systems. Throughout, keep a temporary mitigation in place on anything you cannot patch immediately so there is no window of total exposure. This risk-based order gets the most dangerous gaps closed first while the longer tail of bundled software works its way through vendor updates.
Coordinating Across Teams
In any organisation larger than a handful of servers, patching Log4j is a coordination problem, not just a technical one. The vulnerable code is owned by many different teams, embedded in applications, container images, build pipelines, and third-party products, and no single person can fix it all. The teams that moved fastest set up a shared tracker, assigned each finding an owner, and reported progress against the exposure tiers daily. They also agreed a clear definition of done, so a finding was not closed until both a version check and a re-scan had passed. This shared discipline prevented the common failure mode where everyone assumes someone else has handled a particular system. Pair that coordination with the technical steps above and the long tail of bundled software gets chased to genuine completion rather than drifting indefinitely.
Conclusion
Patching Log4j means upgrading to 2.17.1 on modern Java or the 2.12.4 and 2.3.2 backports on older runtimes, forcing the version across every transitive and bundled copy, and then proving the fix with both a version check and a behaviour scan. The recurring theme is that patching is a verification exercise as much as a build exercise: the change is only real once you have confirmed it on the running system, not merely in your source. Build the verification into your rollout from the start so that no host is ever marked done on the strength of a successful build alone, and so a forgotten bundled copy cannot quietly leave you exposed after you believed the work was finished. The recurring theme is that patching is a verification exercise as much as a build exercise: the change is only real once you have confirmed it on the running system, not merely in your source. Pin the version, prevent regressions, and hunt your logs before closing out. Start by confirming where you stand with the Log4Shell scanner at log4shell.tools.