The Challenge: Modifying Nested Archives in Pure Java

Working with compressed archives in Java is straightforward using the java.nio.file.FileSystem API introduced in Java 7 and refined in Java 8. However, an issue arises when attempting to modify a file located within a nested archive—such as updating file_to_replace.txt inside child.zip, which itself resides within root.zip.

If you attempt to pass a Path object representing a nested zip file directly into FileSystems.newFileSystem(), Java throws the following exception:

java.nio.file.ProviderNotFoundException: Provider not found
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:407)

Why Does ProviderNotFoundException Happen?

The standard Zip File System Provider in Java expects a file system URI (like jar:file:/path/to/archive.zip) or a direct path on the host operating system's default file system. It cannot directly read or write to a Zip file system implementation wrapped inside another custom FileSystem instance without extracting it first.

Since third-party dependencies are restricted and space usage must be kept minimal, the best approach is a temporary extraction pattern using Java 8 standard NIO features.

The Solution: Extract, Modify, Re-inject

To safely update the nested archive without loading everything into memory or using external libraries, follow these steps:

  1. Open the outer archive (root.zip) as a FileSystem.
  2. Extract the target inner archive (child.zip) to a temporary file on disk.
  3. Open the temporary child.zip as a secondary FileSystem.
  4. Perform the file modification inside the temporary child.zip and close its file system to flush changes.
  5. Copy the updated temporary child.zip back into root.zip, replacing the old entry.
  6. Delete the temporary file.

Java 8 Implementation

Here is a complete, production-ready solution that uses only standard Java 8 APIs:

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class NestedZipUpdater {

    public static void updateNestedZip(
            Path rootZip,
            String childZipName,
            String targetFileName,
            Path replacementFile) throws IOException {

        // 1. Open the outer zip file system
        try (FileSystem rootFs = FileSystems.newFileSystem(rootZip, (ClassLoader) null)) {
            Path childInRoot = rootFs.getPath(childZipName);

            if (!Files.exists(childInRoot)) {
                throw new IllegalArgumentException("Child zip not found inside root zip: " + childZipName);
            }

            // 2. Extract child.zip to a temporary file
            Path tempChildZip = Files.createTempFile("nested_zip_", ".zip");
            try {
                Files.copy(childInRoot, tempChildZip, StandardCopyOption.REPLACE_EXISTING);

                // 3. Open the temp child zip file system
                try (FileSystem childFs = FileSystems.newFileSystem(tempChildZip, (ClassLoader) null)) {
                    Path targetInChild = childFs.getPath(targetFileName);
                    
                    // 4. Replace the target file inside child.zip
                    Files.copy(replacementFile, targetInChild, StandardCopyOption.REPLACE_EXISTING);
                } // Closing childFs commits changes to tempChildZip

                // 5. Copy the updated child.zip back into root.zip
                Files.copy(tempChildZip, childInRoot, StandardCopyOption.REPLACE_EXISTING);
            } finally {
                // 6. Ensure temporary file cleanup
                Files.deleteIfExists(tempChildZip);
            }
        } // Closing rootFs commits changes to root.zip
    }

    public static void main(String[] args) {
        try {
            Path rootZip = Paths.get("root.zip");
            Path replacement = Paths.get("test.txt");
            updateNestedZip(rootZip, "child.zip", "file_to_replace.txt", replacement);
            System.out.println("Nested zip file updated successfully!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Key Takeaways & Best Practices

  • Resource Management: Always wrap FileSystem instances in try-with-resources blocks. Closing a Zip FileSystem is mandatory because changes are written and committed during closure.
  • Storage Footprint: Using Files.createTempFile() ensures minimal disk usage, extracting only the specific nested archive rather than unpacking the entire root.zip directory.
  • Clean Up: Place Files.deleteIfExists() inside a finally block to prevent temporary file leakage in case of failure during the update process.