Java – Crear PDF desde XML con FOP

La idea de FOP es separar los datos de la maqueta: los datos en un XML, el diseño en una hoja XSL-FO, y FOP los junta y escribe el PDF. Cambiar el aspecto del documento no toca ni una línea de Java.

La dependencia

<dependency>
  <groupId>org.apache.xmlgraphics</groupId>
  <artifactId>fop</artifactId>
  <version>2.11</version>
</dependency>

1. Los datos

<?xml version="1.0" encoding="UTF-8"?>
<factura numero="2026-0042" fecha="7 de octubre de 2026">
  <cliente>Ana Pérez</cliente>
  <lineas>
    <linea concepto="Camiseta" unidades="2" precio="12.50"/>
    <linea concepto="Bufanda" unidades="1" precio="9.00"/>
    <linea concepto="Mochila" unidades="1" precio="34.00"/>
  </lineas>
</factura>

2. La plantilla, en XSL-FO

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

  <xsl:template match="/factura">
    <fo:root>
      <fo:layout-master-set>
        <fo:simple-page-master master-name="A4" page-width="210mm" page-height="297mm"
                               margin="20mm">
          <fo:region-body/>
        </fo:simple-page-master>
      </fo:layout-master-set>

      <fo:page-sequence master-reference="A4">
        <fo:flow flow-name="xsl-region-body">
          <fo:block font-family="Helvetica" font-size="18pt" font-weight="bold"
                    color="#1e64af" space-after="4mm">
            Factura <xsl:value-of select="@numero"/>
          </fo:block>

          <fo:table table-layout="fixed" width="100%" font-size="10pt">
            <fo:table-column column-width="90mm"/>
            <fo:table-column column-width="25mm"/>
            <fo:table-body>
              <xsl:for-each select="lineas/linea">
                <fo:table-row>
                  <fo:table-cell padding="2mm" border-bottom="0.4pt solid #c9d6e4">
                    <fo:block><xsl:value-of select="@concepto"/></fo:block>
                  </fo:table-cell>
                  <fo:table-cell padding="2mm" border-bottom="0.4pt solid #c9d6e4">
                    <fo:block text-align="right"><xsl:value-of select="@precio"/> €</fo:block>
                  </fo:table-cell>
                </fo:table-row>
              </xsl:for-each>
            </fo:table-body>
          </fo:table>
        </fo:flow>
      </fo:page-sequence>
    </fo:root>
  </xsl:template>
</xsl:stylesheet>

XSL-FO es un vocabulario XML para describir páginas: márgenes, bloques, tablas, tipografías. Las plantillas se escriben una vez y se reutilizan; lo que cambia en cada documento es el XML de datos.

3. El programa

import java.io.*;
import java.nio.file.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.*;

public class CrearPdf {
    public static void main(String[] args) throws Exception {
        FopFactory fabrica = FopFactory.newInstance(new File(".").toURI());

        try (OutputStream salida = new BufferedOutputStream(
                Files.newOutputStream(Path.of("factura.pdf")))) {

            Fop fop = fabrica.newFop(MimeConstants.MIME_PDF, salida);

            Transformer t = TransformerFactory.newInstance()
                    .newTransformer(new StreamSource(new File("factura.xsl")));

            t.transform(new StreamSource(new File("ventas.xml")),
                        new SAXResult(fop.getDefaultHandler()));
        }
    }
}
salidaINFO: Rendered page #1.
factura.pdf: 5912 bytes
cabecera: %PDF-1.4

Cómo encajan las piezas

El Transformer es el de siempre, el del JDK: aplica la hoja XSL al XML. Lo distinto es el destino. En vez de escribir el resultado en un archivo, se le pasa un SAXResult conectado al manejador de FOP, de modo que el XSL-FO que va saliendo de la transformación entra directo en el motor que dibuja el PDF. Nunca existe un archivo intermedio.

El URI que recibe FopFactory.newInstance es la base desde la que se resuelven las rutas relativas de las imágenes y de la configuración. Si las imágenes del PDF no aparecen, casi siempre es eso.

Otros formatos de salida

ConstanteSale
MIME_PDFun PDF
MIME_POSTSCRIPTPostScript, para imprenta
MIME_PNGuna imagen por página
MIME_PLAIN_TEXTtexto plano
MIME_RTFRTF, que abre Word

La misma plantilla sirve para todos: solo cambia la constante.

¿FOP o escribir el PDF directamente?

FOPUna biblioteca de PDF
Los datos ya son XMLencaja solohay que recorrerlos
El diseño lo cambia otra personatoca el XSL, no el Javatoca el código
Documentos largos paginadoslo resuelve éllo paginas tú
Curva de aprendizajehay que aprender XSL-FOes código normal
Pesovarios megas de dependenciasun jar

La regla: si los datos ya vienen en XML y el documento es un informe o una factura con muchas páginas, FOP. Si lo que necesitas es una etiqueta, un recibo corto o algo muy dinámico, sale más a cuenta una biblioteca que escriba el PDF directamente, o incluso convertir HTML con una herramienta como openhtmltopdf.

FOP también tiene línea de órdenes, y para probar una plantilla es lo más rápido: fop -xml ventas.xml -xsl factura.xsl -pdf factura.pdf. No hace falta escribir nada de Java para ver si la maqueta funciona.

Más información en inglés: Create PDF with Apache FOP 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.

1 comentario en «Java – Crear PDF desde XML con FOP»

  1. Sólo comentar 2 cosas:
    1.- FopFactory fopFactory = FopFactory.newInstance(new File(«.»).toURI()) Al pasar por esta línea indica que hay un error, que no encuentra el archivo «.». En la explicación tampoco indica si se debe cambiar por otra cosa.
    2.- En la versión version 1.7 de MIMEConstants.JAVA ya no existe MimeConstants.MIME_PDF por lo que había que cambiar por algo como «application/pdf»

    Responder

Deja un comentario