🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG// DOC-DERIVED — single line, no variance; companion messages differ by tool: java.util.zip.ZipException: zip END header not found at java.base/java.util.zip.ZipFile.findEND(ZipFile.java:533) at java.base/java.util.zip.ZipFile.open(ZipFile.java:613) # same root cause in other tools Error: Unable to initialize main class ... Caused by: java.util.zip.ZipException Gradle: Could not expand ZIP 'guava-33.0.jar'. > zip END header not found

⚡ Quick Fix Works 80% of the time

Delete the corrupted artifact from the local cache and re-resolve with checksum verification — the file on disk is simply not a valid zip.

# Gradle rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.guava && ./gradlew --refresh-dependencies # Maven rm -rf ~/.m2/repository/com/google/guava/guava && mvn -U clean package

🧠 Why this Happens

Tap to expand the deep technical explanation

A valid zip ends with an END-of-central-directory record — 22+ bytes whose signature the ZipFile scanner searches for from the tail backwards. When a download is cut short by network failure, when a captive portal returns its HTML login page in place of the binary, or when disk-full truncation strikes mid-write, that record never lands and the scan fails before any class is read. The JVM never even reaches your code; this is packaging-level rejection during jar opening.

The HITEC City Parking Spot Analogy:

You order a book online; the courier delivers only the first half. Flipping to the index to find chapters is impossible — there is no back cover, let alone an index.

🔁 How to Reproduce Confirm this is your error

Take any healthy jar, truncate it with head -c 5000, place it on the classpath under the expected name, and run anything that loads from it. DOC-DERIVED — deterministic single-line failure.

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Purge the cached copy and re-download with checksums on

👉 Use this first — in nine out of ten cases the build cache holds the corpse.

Build tools keep artifacts forever once fetched; deleting just the offending group directory forces re-resolution while preserving everything else. Re-run with checksum policies enabled so silent corruption cannot recur.

# locate + purge + refetch ls -la ~/.gradle/caches/modules-2/files-2.1/com.google.guava/ rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.guava ./gradlew build --refresh-dependencies # Maven equivalent with strict checksums (fail on mismatch) mvn clean package -U -Dmaven.wagon.httpconnectionManager.ttlSeconds=25
Solution 2

Identify exactly which jar is rotten with a loop validation pass

👉 Use this when the stack trace does not name the file or many jars live in lib/.

Opening every candidate with jar tf (or unzip -t) finds the broken one mechanically instead of guessing from partial logs.

for j in lib/*.jar; do jar tf "$j" > /dev/null 2>&1 || echo "CORRUPT: $j" done
Solution 3

Check the download path: proxies, VPN portals, mirrors

👉 Use this when corruption returns after every re-download.

Corporate proxies and captive portals substitute HTML for binaries; the saved file starts with "<html" instead of PK. Bypassing the proxy or whitelisting repo hosts stops the substitution at the source.

head -c 100 broken.jar | xxd | head -3 # 3c 68 74 6d 6c ... = "<html" -> you received a web page, not a jar
Solution 4

Verify integrity end-to-end in CI

👉 Use this to make recurrence impossible across machines and images.

Enable strict checksums so a tampered or truncated artifact fails resolution loudly instead of landing in the local cache. The CLI flag covers one build; the repository policy makes it permanent for every developer and CI runner.

# CLI - fail the build on any checksum mismatch mvn -C clean package # pom.xml / settings.xml - enforce per repository, permanently <releases> <enabled>true</enabled> <checksumPolicy>fail</checksumPolicy> </releases>
Solution 5

Extract manually to confirm and salvage classes

👉 DEV ONLY — forensic use only.

DEV ONLY. unzip tolerates missing central directories far better than the JVM and can sometimes recover entries from intact local headers. Useful for a one-off rescue of a vendor jar nobody can re-fetch; never rebuild shipping artifacts from salvaged output.

# DEV ONLY - forensics zip -FF broken.jar --out repaired.jar && unzip -l repaired.jar | head

📋 Version Notes

Java 8

Same exception; message text identical since forever.

Java 11

Unchanged.

Java 17

findEND internals reworked but public wording identical.

Java 21

Unchanged; multi-release jars still require intact central directory.

🛡️ How to Prevent This Next Time

Strict checksum enforcement in builds, stable internal mirrors behind authenticated hosts, disk-space monitoring on build agents, and a CI step that validates every jar entering a release image.