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.
También te puede interesar
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.
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
| Constante | Sale |
|---|---|
MIME_PDF | un PDF |
MIME_POSTSCRIPT | PostScript, para imprenta |
MIME_PNG | una imagen por página |
MIME_PLAIN_TEXT | texto plano |
MIME_RTF | RTF, que abre Word |
La misma plantilla sirve para todos: solo cambia la constante.
¿FOP o escribir el PDF directamente?
| FOP | Una biblioteca de PDF | |
|---|---|---|
| Los datos ya son XML | encaja solo | hay que recorrerlos |
| El diseño lo cambia otra persona | toca el XSL, no el Java | toca el código |
| Documentos largos paginados | lo resuelve él | lo paginas tú |
| Curva de aprendizaje | hay que aprender XSL-FO | es código normal |
| Peso | varios megas de dependencias | un 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 -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.
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»