código·java·oracle
Java y JVM

Creación de un fichero PDF en Java con iText

Facturas, informes, albaranes: tarde o temprano toda aplicación acaba generando PDF. La API moderna de iText 7 convierte el trámite en veinte líneas legibles.

publicado diciembre de 2011 revisado agosto de 2026 635 palabras

Generar un PDF fue durante años un rito oscuro: librerías de API cavernosa, fuentes embebidas a mano y tablas que se rompían sin motivo. La librería iText llevó el trámite al mundo Java a principios de los 2000 y su API actual, la de la versión 7, es un ejemplo de diseño limpio: documento, párrafo, tabla, cerrar y listo. Este artículo monta una factura completa en veinte líneas y repasa los detalles que separan un PDF de producción de un juguete.

Las tres piezas del rompecabezas

iText 7 separa tres conceptos que en las versiones antiguas se mezclaban: el PdfWriter sabe escribir bytes en un destino, el PdfDocument entiende la estructura del formato (páginas, fuentes, objetos), y el Document es la capa de alto nivel con márgenes y flujo de texto:

JAVAFacturaMinima.java
PdfWriter writer = new PdfWriter("factura.pdf");
PdfDocument pdf  = new PdfDocument(writer);
Document  doc    = new Document(pdf);

doc.add(new Paragraph("Factura 2026-042").setFontSize(18));
doc.add(new Paragraph("Cliente: ACME S.L.").setFontSize(11));

doc.close();   // cierra tambien PdfDocument y writer

El orden importa y el close es sagrado: sin él, el fichero queda sin el trailer del formato y ningún lector lo abre. El try-with-resources del ejemplo completo lo garantiza incluso con excepciones.

Una factura de verdad, con tabla

JAVAFacturaCompleta.java
try (PdfWriter writer = new PdfWriter("factura.pdf");
     PdfDocument pdf  = new PdfDocument(writer);
     Document  doc    = new Document(pdf, PageSize.A4)) {

    doc.add(new Paragraph("Factura 2026-042")
              .setFontSize(16).setBold());
    doc.add(new Paragraph("Fecha: 2026-08-17"));

    Table tabla = new Table(
        UnitValue.createPercentArray(new float[]{4, 1, 1, 2}))
        .useAllAvailableWidth();

    Stream.of("Concepto", "Uds", "Precio", "Importe")
          .forEach(h -> tabla.addHeaderCell(
              new Paragraph(h).setBold()));

    tabla.addCell(new Paragraph("Licencia JVM"));
    tabla.addCell("3");
    tabla.addCell("80,00");
    tabla.addCell("240,00");

    tabla.addCell(new Paragraph("Soporte anual"));
    tabla.addCell("1");
    tabla.addCell("120,00");
    tabla.addCell("120,00");

    doc.add(tabla);
    doc.add(new Paragraph("Total: 360,00 EUR")
              .setBold().setMarginTop(8));
}

Las anchuras en porcentaje reparten el ancho útil entre columnas, addHeaderCell marca la fila que se repite en cada página —iText parte la tabla sola cuando no cabe— y los márgenes por elemento se ajustan con setMarginTop y familia. Con esa base, informes completos son composición de piezas, no lucha contra la librería.

Fuentes: el detalle incómodo

El PDF estándar garantiza catorce fuentes base (Helvetica, Times, Courier...), pero no incluye acentos latinos extendidos ni símbolos como el euro en todas ellas. Para texto español serio, o embebes una fuente TrueType del sistema o usas los paquetes de fuentes de iText:

JAVAFuentes.java
PdfFont normal = PdfFontFactory.createFont(
    "src/main/resources/fonts/Regular.ttf",
    PdfEncodings.IDENTITY_H);   // soporte unicode completo

doc.setFont(normal);
doc.add(new Paragraph("Importe: 360,00 € — pág. 1 de 1"));

Sin IDENTITY_H, la primera «€» o la primera «ó» mal codificada aparece como cuadrado en el PDF del cliente, descubrimiento clásico de un viernes por la tarde.

Licencias y alternativas, dicho claro

  • iText 7 es AGPL: uso gratuito si tu aplicación también libera su código; para software propietario hay licencia comercial. Las versiones 2.x (com.lowagie), antiguas y LGPL, circulan todavía en proyectos heredados con la API de una sola clase Document.
  • Apache PDFBox: licencia Apache, API algo más bruta, perfecto si la licencia de iText estorpa.
  • OpenPDF: bifurcación comunitaria de iText 4, mantiene la API clásica con licencia LGPL/MPL.

Generación bajo demanda en una aplicación web

El patrón de producción: en un servlet o controlador, escribir sobre un ByteArrayOutputStream en vez de sobre fichero, y devolverlo con application/pdf y Content-Disposition: inline o attachment. Los datos salen de la base de datos —por ejemplo con las sentencias preparadas del artículo de JDBC con Oracle— y el PDF se compone en memoria: nada de ficheros temporales en disco que limpiar.

Un PDF generado es una promesa de que tu factura, informe o albarán se verá igual dentro de diez años. Merece las veinte líneas y una fuente bien embebida.

En la misma familia de «resultados que se tocan»: la reflexión que muchos generadores usaban para volar objetos a documentos, y el pequeño juego en Java para el otro extremo del espectro, dibujar píxeles por diversión.