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.
También te puede interesar
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.
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 normal | XSSFWorkbook |
| Un .xls antiguo | HSSFWorkbook |
| Cientos de miles de filas | SXSSFWorkbook |
| Formato de moneda o fecha | CellStyle + createDataFormat() |
| Una fórmula | celda.setCellFormula("SUM(...)"), sin el igual |
| Que no se corte el texto | hoja.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.
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?
Se guarda en la ruta del proyecto. Pero el código me da varios fallos
Habría que cambiar a formato xls y no xlsx ( no lo abre)
Y el otro fallo que me daba era que tengo que cambiar esta línea
Set keyset = datos.keySet();
pudiste arreglar lo de Set keyset = datos.keySet();? tengo el mismo problema.
saludos
para arreglar eso Set keyset = datos.keySet();? deben cambiar el String por Object.