Java – Crear archivo Excel

Apache POI escribe archivos de Excel de verdad, con estilos y fórmulas, sin tener Excel instalado. Para el formato moderno .xlsx la clase es XSSFWorkbook.

La dependencia

<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.5.1</version>
</dependency>

poi-ooxml es la de los formatos nuevos (.xlsx). Si solo necesitas el formato antiguo .xls, basta poi a secas y la clase HSSFWorkbook.

Una hoja con estilo y fórmulas

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.time.LocalDate;

public class CrearExcel {
    record Venta(String producto, int unidades, double precio, LocalDate fecha) {}

    public static void main(String[] args) throws Exception {
        var ventas = new Venta[] {
            new Venta("Camiseta", 12, 25.00, LocalDate.of(2026, 10, 1)),
            new Venta("Bufanda",   3,  9.00, LocalDate.of(2026, 10, 2)),
            new Venta("Mochila",   1, 34.00, LocalDate.of(2026, 10, 3)),
        };

        try (Workbook libro = new XSSFWorkbook()) {
            Sheet hoja = libro.createSheet("Ventas");

            CellStyle cabecera = libro.createCellStyle();
            Font negrita = libro.createFont();
            negrita.setBold(true);
            negrita.setColor(IndexedColors.WHITE.getIndex());
            cabecera.setFont(negrita);
            cabecera.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
            cabecera.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cabecera.setAlignment(HorizontalAlignment.CENTER);

            CellStyle euros = libro.createCellStyle();
            euros.setDataFormat(libro.createDataFormat().getFormat("#,##0.00\\ \"€\""));

            CellStyle fecha = libro.createCellStyle();
            fecha.setDataFormat(libro.createDataFormat().getFormat("dd/mm/yyyy"));

            Row r0 = hoja.createRow(0);
            String[] titulos = {"Producto", "Unidades", "Precio", "Importe", "Fecha"};
            for (int c = 0; c < titulos.length; c++) {
                Cell cel = r0.createCell(c);
                cel.setCellValue(titulos[c]);
                cel.setCellStyle(cabecera);
            }

            int f = 1;
            for (Venta v : ventas) {
                Row r = hoja.createRow(f);
                r.createCell(0).setCellValue(v.producto());
                r.createCell(1).setCellValue(v.unidades());

                Cell precio = r.createCell(2);
                precio.setCellValue(v.precio());
                precio.setCellStyle(euros);

                Cell importe = r.createCell(3);
                importe.setCellFormula("B" + (f + 1) + "*C" + (f + 1));
                importe.setCellStyle(euros);

                Cell dia = r.createCell(4);
                dia.setCellValue(v.fecha());
                dia.setCellStyle(fecha);
                f++;
            }

            Row total = hoja.createRow(f);
            total.createCell(0).setCellValue("Total");
            total.getCell(0).setCellStyle(cabecera);
            Cell suma = total.createCell(3);
            suma.setCellFormula("SUM(D2:D" + f + ")");
            suma.setCellStyle(euros);

            for (int c = 0; c < titulos.length; c++) hoja.autoSizeColumn(c);
            hoja.createFreezePane(0, 1);

            try (var out = new FileOutputStream("ventas.xlsx")) {
                libro.write(out);
            }
        }
    }
}
salidaventas.xlsx: 3862 bytes

Lo que hay que saber de POI

Los estilos se crean una vez y se reparten. Un CellStyle se pide al libro, no a la celda, y el mismo objeto se asigna a todas las celdas que lo compartan. Crear uno por celda dentro del bucle es el error de rendimiento clásico: un .xlsx admite como mucho 64.000 estilos y una hoja mediana los agota.

Las fórmulas se escriben sin el igual. setCellFormula("SUM(D2:D4)"), no "=SUM(...)". Y las referencias son en base 1, mientras que las filas de POI empiezan en 0: de ahí el f + 1. POI no calcula el resultado al escribir; lo calcula Excel al abrir, o tú con un FormulaEvaluator.

Las fechas son números por dentro. setCellValue(LocalDate) guarda un número de serie; sin un CellStyle con formato de fecha, en Excel se ve 46296. El formato no es decoración: es lo que la hace legible.

autoSizeColumn hay que llamarlo al final, cuando ya están los datos, y es lento en hojas grandes porque mide cada celda. createFreezePane(0, 1) deja la primera fila fija al desplazarse.

Dos avisos que vas a ver en la consola. POI arrastra Log4j como interfaz y, si no hay ninguna implementación en el proyecto, imprime ERROR Log4j API could not find a logging provider y sigue funcionando. Se quita añadiendo log4j-core o log4j-to-slf4j. Y poi-ooxml arrastra unas cuantas dependencias —xmlbeans, commons-compress, commons-collections4—: con Maven vienen solas, pero si compilas a mano hay que bajarlas todas.

Cuando la hoja es enorme

try (var libro = new SXSSFWorkbook(100)) {   // solo 100 filas en memoria
    // ... igual que antes ...
    libro.dispose();
}

SXSSFWorkbook va escribiendo a disco y mantiene en memoria solo una ventana de filas. Con cientos de miles de filas es la diferencia entre funcionar y quedarse sin memoria. A cambio, no puedes volver a una fila que ya salió de la ventana.

Para…Usa
Un .xlsx normalXSSFWorkbook
Un .xls antiguoHSSFWorkbook
Cientos de miles de filasSXSSFWorkbook
Formato de moneda o fechaCellStyle + createDataFormat()
Una fórmulacelda.setCellFormula("SUM(...)"), sin el igual
Que no se corte el textohoja.autoSizeColumn(c) al final

Más información en inglés: Create Excel file with Apache POI.

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.

5 comentarios en «Java – Crear archivo Excel»

  1. Estimados, gracias por su maravillosa acotación.
    Ahora bien, necesito vuestra ayuda respecto a la ruta donde sera guardado el archivo excel. Como podría hacer para que el usuario escoja la ruta donde desea se guarde el archivo excel generado?

    Responder

Deja un comentario