Java – Crear archivos ZIP

El JDK trae java.util.zip desde la versión 1.1, así que para hacer un ZIP no hace falta ninguna dependencia. La pieza es ZipOutputStream: se abre, se va añadiendo una entrada por archivo y se cierra.

Comprimir una carpeta entera

import java.io.*;
import java.nio.file.*;
import java.util.zip.*;

public class CrearZip {
    public static void main(String[] args) throws Exception {
        Path carpeta = Path.of("origen");
        Path zip = Path.of("copia.zip");

        try (var salida = new ZipOutputStream(Files.newOutputStream(zip));
             var rutas = Files.walk(carpeta)) {

            salida.setLevel(Deflater.BEST_COMPRESSION);

            rutas.filter(Files::isRegularFile).sorted().forEach(p -> {
                try {
                    salida.putNextEntry(new ZipEntry(carpeta.relativize(p).toString()));
                    Files.copy(p, salida);
                    salida.closeEntry();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        }
        System.out.println("creado " + zip + ", " + Files.size(zip) + " bytes");
    }
}
salidacreado copia.zip, 365 bytes

Las tres cosas que hay que hacer bien

1. La ruta de la entrada tiene que ser relativa. Ese carpeta.relativize(p) convierte origen/sub/c.txt en sub/c.txt. Si metes la ruta absoluta, el ZIP guarda /home/usuario/proyecto/origen/... dentro y al descomprimirlo en otro ordenador aparece un árbol de carpetas absurdo. Los ZIP usan siempre la barra /, también los creados en Windows.

2. Una entrada se abre y se cierra. putNextEntry y closeEntry van en pareja; entre medias se escribe el contenido, y Files.copy(p, salida) lo vuelca sin bucles.

3. El try (…) cierra el ZIP. Si no se cierra, el archivo queda truncado y ningún programa lo abre: la tabla de contenidos de un ZIP se escribe al final.

Comprobar lo que ha quedado dentro

try (var zf = new ZipFile(zip.toFile())) {
    zf.stream().forEach(e -> System.out.printf("  %-14s %4d -> %4d bytes%n",
            e.getName(), e.getSize(), e.getCompressedSize()));
}
salidaa.txt             8 ->   10 bytes
  b.txt             8 ->   10 bytes
  sub/c.txt         7 ->    9 bytes
Sí, han crecido. Un archivo de ocho bytes ocupa diez comprimido, porque el algoritmo añade su propia cabecera y no hay nada que repetir en tan poco texto. La compresión gana con archivos grandes y repetitivos —registros, CSV, HTML— y no hace nada con lo que ya está comprimido: un JPEG o un MP4 dentro de un ZIP ocupan lo mismo o un poco más.

El nivel de compresión

ValorQué hace
Deflater.NO_COMPRESSION (0)solo empaqueta, no comprime
Deflater.DEFAULT_COMPRESSION (−1)el equilibrio de serie
Deflater.BEST_SPEED (1)rápido y flojo
Deflater.BEST_COMPRESSION (9)lento y apretado

Entre el 1 y el 9 la diferencia de tamaño suele ser de un 5 % y la de tiempo, de varias veces. Para un ZIP que se genera al vuelo en un servidor, el nivel por omisión es casi siempre la decisión correcta.

Comprimir desde memoria

var buffer = new ByteArrayOutputStream();
try (var zip = new ZipOutputStream(buffer)) {
    zip.putNextEntry(new ZipEntry("informe.csv"));
    zip.write(csv.getBytes(StandardCharsets.UTF_8));
    zip.closeEntry();
}
byte[] descarga = buffer.toByteArray();

Útil para devolver un ZIP desde una web sin dejar nada en disco. Si el contenido es grande, mejor escribir directamente en el OutputStream de la respuesta.

Más información en inglés: Create ZIP files in Java.

Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.