Maven Corrupt JARs: Delete and Re-download Dependencies

Maven keeps using a corrupt JAR or a cached failed download until we remove it. Learn which error means which problem, when mvn -U helps, and how to clean the local repository on any OS.

maven remove corrupt jars

A corrupt JAR in Maven is a dependency file in the local repository (~/.m2/repository) that is damaged or was never downloaded completely, and Maven keeps using it because the file is already there. The same happens with a failed download, which Maven remembers in a .lastUpdated file and does not retry for a day. The fix is to delete the broken files and let Maven download them again.

We need this fix when a build fails with errors such as zip END header not found or was cached in the local repository, even though the dependency exists in the remote repository.

The following example shows the three commands that fix most cases, with the effect of each one as a comment.

# 1. Retry missing artifacts whose failed download Maven cached
mvn clean package -U

# 2. Delete all failed-download markers (Linux and macOS)
find ~/.m2/repository -name "*.lastUpdated" -delete

# 3. Delete one damaged dependency of the project and download it again
mvn dependency:purge-local-repository -Dinclude=com.google.code.gson:gson

Notice that -U only helps with cached failures. A damaged JAR of a release version stays in place until we delete it, which the third command does.

Next, we look at how each kind of broken file shows up in the build output, and how to fix it on Unix-like systems and on Windows. At the end, we build a small Java program that finds every corrupt JAR in the local repository.

1. Why Maven Keeps Using a Broken Dependency

Maven downloads every dependency once and stores it in the local repository, which is ~/.m2/repository by default. On the next build, Maven finds the file in the local repository and does not contact the remote repository again. Two kinds of files break this cache.

  • A .lastUpdated file records that a download failed. Maven reads it and does not retry the download until the update interval has passed, which is daily by default.
  • A corrupt JAR is a file that exists but is not a valid JAR, for example a file cut off by an interrupted download or damaged on disk. Maven does not check the content of a release JAR it already has, so it never replaces the file.

Each kind gives a different error message, so the message tells us which fix to use.

Error message in the buildCauseFix
was not found in … during a previous attempt. This failure was cached in the local repository.lastUpdated filemvn -U, or delete the .lastUpdated files
Could not transfer artifact … Connection refusednetwork error, Maven writes a .lastUpdated filemvn -U once the network works
error reading … .jar; zip END header not foundJAR cut offdelete the version folder or run purge-local-repository
invalid LOC header (bad signature), SHA-1 mismatchJAR content damageddelete the version folder or run purge-local-repository
Decision flow for a failing Maven build. If the error says the failure was cached in the local repository, run mvn -U or delete the .lastUpdated files. If the error says zip END header not found or invalid LOC header, delete the version folder or run dependency:purge-local-repository. If several JARs are damaged, run the CleanRepo program.
The error message decides the fix; -U repairs cached failures, but a damaged JAR has to be deleted.

2. Fixing Failed Downloads and .lastUpdated Files

When Maven cannot download a file, it writes a .lastUpdated file next to the place where the file should be. The file holds the time of the attempt and the error, so Maven can skip the same request in the next build.

In a team, this caching shows up when a dependency is published after our first try. For example, our app depends on version 1.99.0 of an internal library, and we build before the release pipeline has deployed it. The build fails, the release is deployed ten minutes later, and our next build still fails without contacting the repository.

The following example uses a version of commons-text that does not exist to show the same behavior with Maven 3.9.16. The first build asks Maven Central and fails.

$ mvn package
[ERROR] Failed to execute goal on project recipe-app: Could not resolve dependencies for project com.howtodoinjava:recipe-app:jar:1.0.0
[ERROR] dependency: org.apache.commons:commons-text:jar:1.99.0 (compile)
[ERROR] 	Could not find artifact org.apache.commons:commons-text:jar:1.99.0 in central (https://repo.maven.apache.org/maven2)

The second build does not ask Maven Central at all. The message changes, and it names the cache as the reason.

$ mvn package
[ERROR] 	org.apache.commons:commons-text:jar:1.99.0 was not found in https://repo.maven.apache.org/maven2 during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of central has elapsed or updates are forced

The version folder contains no JAR, only two marker files, one for the JAR and one for the POM.

$ ls ~/.m2/repository/org/apache/commons/commons-text/1.99.0/
commons-text-1.99.0.jar.lastUpdated  commons-text-1.99.0.pom.lastUpdated

$ cat commons-text-1.99.0.jar.lastUpdated
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Sun Oct 04 20:46:41 IST 2026
https\://repo.maven.apache.org/maven2/.error=
https\://repo.maven.apache.org/maven2/.lastUpdated=1791127001208

2.1. Forcing an Update With -U

The -U option (long form –update-snapshots) forces Maven to check the remote repositories for missing releases and updated snapshots. It ignores the .lastUpdated markers for this build, so Maven asks Maven Central again. In our case, the artifact still does not exist, and the message is back to Could not find artifact, which proves that Maven made a new request.

$ mvn package -U
[ERROR] 	Could not find artifact org.apache.commons:commons-text:jar:1.99.0 in central (https://repo.maven.apache.org/maven2)

When the artifact exists by then, the same command downloads it and the build passes. We use -U for one build only, because it also checks every snapshot dependency and makes the build slower.

2.2. Deleting .lastUpdated Files

Deleting the markers has the same effect as -U for the next build, without the extra checks for snapshot updates. On Linux and macOS, the find command lists and deletes them in one step.

$ find ~/.m2/repository -name "*.lastUpdated" -print -delete
/root/.m2/repository/org/apache/commons/commons-text/1.99.0/commons-text-1.99.0.pom.lastUpdated
/root/.m2/repository/org/apache/commons/commons-text/1.99.0/commons-text-1.99.0.jar.lastUpdated
/root/.m2/repository/org/apache/commons/commons-text/1.15.0/commons-text-1.15.0.pom.lastUpdated
/root/.m2/repository/org/apache/maven/plugins/maven-clean-plugin/3.2.0/maven-clean-plugin-3.2.0.pom.lastUpdated

On Windows, we run the PowerShell version. The -Verbose flag prints one line for every deleted file.

Get-ChildItem "$env:USERPROFILE\.m2\repository" -Recurse -Filter *.lastUpdated | Remove-Item -Verbose

If the local repository is in another folder, we replace the path with the localRepository value from the Maven settings file.

3. Fixing a Corrupt JAR File

A corrupt JAR causes an error at the first plugin that reads it, which is the compiler in most builds. Say a developer starts the first build of a project on a train, and the connection drops while a large JAR downloads. The file in the local repository is cut off, and every later build fails at the same place.

The following example cuts the Gson JAR in the local repository to 150,000 of its 313,604 bytes, the way a broken download leaves it. The compiler cannot read the file.

$ mvn clean package
[ERROR] COMPILATION ERROR :
[ERROR] error reading /root/.m2/repository/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar; zip END header not found
[INFO] BUILD FAILURE

The -U option does not fix a corrupt release JAR, because Maven finds the file in the local repository and never downloads a release version twice. Running mvn clean package -U on the same project fails with the same zip END header not found error.

3.1. Using dependency:purge-local-repository

The purge-local-repository goal of the Maven Dependency Plugin deletes dependencies of the current project from the local repository and downloads them again. The include property limits it to the broken artifact, so the other dependencies stay in place.

$ mvn dependency:purge-local-repository -Dinclude=com.google.code.gson:gson
[INFO] --- dependency:3.7.0:purge-local-repository (default-cli) @ recipe-app ---
[INFO] Deleting 1 transitive dependency for project recipe-app from /root/.m2/repository with artifact version resolution fuzziness
[INFO] Re-resolving dependencies
[INFO] BUILD SUCCESS

After the goal, the JAR has its full size of 313,604 bytes again, and mvn clean package passes. Without the include property, the goal deletes and downloads every dependency of the project, which also works but takes longer.

Outside a project folder, we use manualInclude instead. It does not need a pom.xml, but it deletes every version of the artifact and downloads nothing, so the next build downloads the version it needs.

$ mvn dependency:purge-local-repository -DmanualInclude=com.google.code.gson:gson
[INFO] --- dependency:3.7.0:purge-local-repository (default-cli) @ standalone-pom ---
[INFO] Deleting 1 manual dependency from /root/.m2/repository
[INFO] BUILD SUCCESS

3.2. Deleting the Version Folder by Hand

When the dependency plugin itself cannot run, for example because one of its own JARs is corrupt, we delete the folder of the broken version. The folder path is the group ID with dots replaced by slashes, followed by the artifact ID and the version.

# Linux and macOS
rm -rf ~/.m2/repository/com/google/code/gson/gson/2.14.0

# Windows PowerShell
Remove-Item -Recurse -Force "$env:USERPROFILE\.m2\repository\com\google\code\gson\gson\2.14.0"

We delete the whole version folder, not only the JAR. The folder also holds the POM, the .sha1 checksum files and the _remote.repositories file, and Maven downloads all of them again on the next build.

4. Finding All Corrupt JARs With a Java Program

After a crash or a full disk, several JARs can be damaged, and the build shows only the first one. Checking the files by hand takes a long time in a local repository with hundreds of JARs.

The following example is a Java 25 program that checks the whole local repository in one run. It is a compact source file (a Java file without a class declaration), so it needs no import statements, and we start it with java CleanRepo.java without compiling it first. The program reports three problems.

  • A .lastUpdated file is reported as a failed download marker.
  • A JAR that java.util.zip.ZipFile cannot open is not a valid ZIP file.
  • A JAR whose SHA-1 hash differs from the hash in its .sha1 file has damaged content. Maven saves this .sha1 file next to each downloaded JAR.

The program only prints the problems by default. With the –delete argument, it deletes each .lastUpdated file and the version folder of each corrupt JAR.

// Usage: java CleanRepo.java [repository folder] [--delete]
void main(String[] args) throws IOException {
  Path repo = args.length > 0 && !args[0].startsWith("--")
      ? Path.of(args[0])
      : Path.of(System.getProperty("user.home"), ".m2", "repository");
  boolean delete = List.of(args).contains("--delete");

  Map<Path, String> problems = new TreeMap<>();
  try (Stream<Path> files = Files.walk(repo)) {
    files.filter(Files::isRegularFile).forEach(file -> {
      String name = file.getFileName().toString();
      if (name.endsWith(".lastUpdated")) {
        problems.put(file, "failed download marker");
      } else if (name.endsWith(".jar")) {
        String error = checkJar(file);
        if (error != null) {
          problems.put(file.getParent(), error + " in " + name);
        }
      }
    });
  }

  problems.forEach((path, reason) -> IO.println(reason + ": " + repo.relativize(path)));
  IO.println(problems.size() + " problem(s) found" + (delete ? ", deleting" : ", dry run"));
  if (delete) {
    for (Path path : problems.keySet()) {
      deleteRecursively(path);
    }
  }
}

String checkJar(Path jar) {
  try (ZipFile zip = new ZipFile(jar.toFile())) {
    zip.size();
  } catch (IOException e) {
    return "not a valid ZIP file";
  }
  Path sha1File = jar.resolveSibling(jar.getFileName() + ".sha1");
  if (Files.exists(sha1File)) {
    try {
      String expected = Files.readString(sha1File).strip().split("\\s+")[0];
      byte[] hash = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(jar));
      if (!HexFormat.of().formatHex(hash).equalsIgnoreCase(expected)) {
        return "SHA-1 mismatch";
      }
    } catch (IOException | NoSuchAlgorithmException e) {
      return "cannot verify (" + e.getMessage() + ")";
    }
  }
  return null;
}

void deleteRecursively(Path path) throws IOException {
  try (Stream<Path> paths = Files.walk(path)) {
    for (Path p : paths.sorted(Comparator.reverseOrder()).toList()) {
      Files.delete(p);
    }
  }
}

The SHA-1 check finds damage that the ZIP check misses. In our test repository, we changed one byte inside the commons-lang3 JAR. The JAR still opens as a ZIP file, but the hash no longer matches. The file checksum comparison works the same way for any file type.

We test the program on a copy of the local repository with the cut-off Gson JAR, the changed commons-lang3 JAR and four .lastUpdated markers. The first run is a dry run.

$ java CleanRepo.java ~/.m2/repository
not a valid ZIP file in gson-2.14.0.jar: com/google/code/gson/gson/2.14.0
SHA-1 mismatch in commons-lang3-3.21.0.jar: org/apache/commons/commons-lang3/3.21.0
failed download marker: org/apache/commons/commons-text/1.15.0/commons-text-1.15.0.pom.lastUpdated
failed download marker: org/apache/commons/commons-text/1.99.0/commons-text-1.99.0.jar.lastUpdated
failed download marker: org/apache/commons/commons-text/1.99.0/commons-text-1.99.0.pom.lastUpdated
failed download marker: org/apache/maven/plugins/maven-clean-plugin/3.2.0/maven-clean-plugin-3.2.0.pom.lastUpdated
6 problem(s) found, dry run

The second run deletes the files, and the next Maven build downloads the two JARs again.

$ java CleanRepo.java ~/.m2/repository --delete
...
6 problem(s) found, deleting

$ mvn clean package
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar (314 kB at 1.8 MB/s)
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar (749 kB at 1.7 MB/s)
[INFO] BUILD SUCCESS

$ java CleanRepo.java ~/.m2/repository
0 problem(s) found, dry run

The program deletes only what it reports, and only with –delete, so we always look at the dry run first. On a local repository with 775 JARs, the check took about 15 seconds, because it reads every JAR once to compute the hash. The recursive delete walks the folder in reverse order, so the files go before their folder.

5. Deleting the Whole Local Repository

Deleting the whole local repository fixes every broken file at once. The cost is that the next build of every project downloads all dependencies and plugins again, which takes minutes and fails without network access. So we use it only when the targeted fixes in section 2 to section 4 do not help.

# Linux and macOS
rm -rf ~/.m2/repository

# Windows PowerShell
Remove-Item -Recurse -Force "$env:USERPROFILE\.m2\repository"

We delete only the repository folder, never the whole .m2 folder. The .m2 folder also holds settings.xml, with the mirrors, proxy settings and passwords for private repositories.

6. Maven Corrupt JAR FAQs

6.1. Does mvn -U Re-download All Dependencies?

No. The -U option checks for missing releases and updated snapshots, and it ignores the cached failures in .lastUpdated files. A release JAR that is already in the local repository stays as it is, even when the file is corrupt, as we saw in section 3.

6.2. Is It Safe to Delete the .m2/repository Folder?

Yes. The local repository is a cache, and Maven downloads everything again on the next build. We lose the JARs that we installed only on our machine with mvn install, so we run mvn install again for those projects. Builds also need network access until the cache is full again.

6.3. How Do I Fix invalid LOC header (bad signature)?

The error means that the JAR content is damaged at the start of one entry, so the compiler cannot read that class. For example, after we changed one byte in the commons-lang3 JAR, the compiler printed this message.

[ERROR] RecipeApp.java:[4,32] cannot access org.apache.commons.lang3.StringUtils
  bad class file: /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar(/org/apache/commons/lang3/StringUtils.class)
    unable to access file: java.util.zip.ZipException: invalid LOC header (bad signature)

We delete the version folder of the JAR named in the error, or run dependency:purge-local-repository with include set to that artifact. The fix is the same as for zip END header not found, as described in section 3.

7. Conclusion

Maven trusts its local repository, so a broken file there breaks every build until we remove it. The error message tells us which kind of file is broken.

For a cached failure, the message mentions the local repository cache, and mvn -U or deleting the .lastUpdated files fixes it. For a corrupt JAR, the message comes from reading the file, for example zip END header not found, and only deleting the file helps, either with dependency:purge-local-repository or by removing the version folder.

When several files are broken, the CleanRepo.java program finds them all, with a dry run first, and deleting the whole local repository stays the last resort.

8. References

Happy Learning !!

Leave a Comment

  1. after capturing the file list , what is the correction that i have to take to make my maven build work perfect

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.