En el desarrollo de aplicaciones web, generar documentos PDF de manera dinámica es una necesidad común, especialmente para reportes, facturas o resúmenes de datos. Una excelente combinación de herramientas para lograr esto en aplicaciones Spring Boot es utilizar Thymeleaf para la generación de plantillas HTML y Flying Saucer para convertirlas en documentos PDF de alta calidad.
En este artículo, te mostraré paso a paso cómo configurar un proyecto de Spring Boot para generar PDFs utilizando Flying Saucer y Thymeleaf, además de compartir algunos consejos útiles para mejorar el formato del documento final. ¡Vamos a ello! 🚀
Flying Saucer es una biblioteca de código abierto basada en Java que permite convertir documentos HTML + CSS en archivos PDF. Es ideal porque permite aprovechar toda la flexibilidad de HTML y CSS para diseñar documentos y luego exportarlos con alta fidelidad al formato PDF.
Para seguir este tutorial, necesitarás:
1. Tener instalado Java 8 o superior.
2. Un proyecto de Spring Boot configurado (puedes usar Spring Initializr).
3. Conocimientos básicos de Thymeleaf, el motor de plantillas que usaremos.
4. Un editor como IntelliJ IDEA o VS Code.
Primero, agrega las dependencias necesarias en tu archivo pom.xml
si utilizas Maven:
<dependencies>
<!-- Thymeleaf para la generación de plantillas HTML -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Flying Saucer para la conversión de HTML a PDF -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.20</version>
</dependency>
<!-- Dependencia para manipulación de fuentes en PDF -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
</dependencies>
Si usas Gradle, agrega lo siguiente en tu build.gradle
:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.xhtmlrenderer:flying-saucer-pdf:9.1.20'
implementation 'org.jsoup:jsoup:1.15.3'
}
Creamos una plantilla HTML en la carpeta src/main/resources/templates/
. Por ejemplo, un archivo llamado invoice.html
con el siguiente contenido:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Factura</title>
<style>
body { font-family: Arial, sans-serif; }
h1 { color: #2c3e50; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1 th:text="${titulo}">Factura</h1>
<p th:text="'Fecha: ' + ${fecha}"></p>
<table>
<thead>
<tr>
<th>Producto</th>
<th>Cantidad</th>
<th>Precio</th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${items}">
<td th:text="${item.nombre}"></td>
<td th:text="${item.cantidad}"></td>
<td th:text="${item.precio}"></td>
</tr>
</tbody>
</table>
</body>
</html>
Esta plantilla utiliza expresiones de Thymeleaf para mostrar datos dinámicos como el título, la fecha y una lista de productos.
Creamos un servicio en Spring Boot para procesar la plantilla Thymeleaf y generar el archivo PDF.
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Map;
@Service
public class PdfService {
private final SpringTemplateEngine templateEngine;
public PdfService(SpringTemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
public byte[] generatePdf(String templateName, Map<String, Object> data) throws Exception {
// Procesar la plantilla Thymeleaf
Context context = new Context();
context.setVariables(data);
String htmlContent = templateEngine.process(templateName, context);
// Convertir el HTML a formato XHTML válido
Document document = Jsoup.parse(htmlContent);
document.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
String xhtml = document.html();
// Generar el PDF usando Flying Saucer
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtml);
renderer.layout();
renderer.createPDF(outputStream);
return outputStream.toByteArray();
}
}
}
Creamos un controlador REST para que los usuarios puedan descargar el PDF generado.
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class PdfController {
private final PdfService pdfService;
public PdfController(PdfService pdfService) {
this.pdfService = pdfService;
}
@GetMapping("/generate-pdf")
public ResponseEntity<byte[]> generatePdf() {
try {
Map<String, Object> data = new HashMap<>();
data.put("titulo", "Factura de compra");
data.put("fecha", "2025-01-21");
data.put("items", List.of(
Map.of("nombre", "Producto A", "cantidad", 2, "precio", 20),
Map.of("nombre", "Producto B", "cantidad", 1, "precio", 15)
));
byte[] pdfBytes = pdfService.generatePdf("invoice", data);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=factura.pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(pdfBytes);
} catch (Exception e) {
return ResponseEntity.internalServerError().body(null);
}
}
}
Levanta tu aplicación de Spring Boot y accede a la URL:
http://localhost:8080/generate-pdf
Esto generará y descargará el archivo factura.pdf.
Generar archivos PDF en aplicaciones Spring Boot con Thymeleaf y Flying Saucer es una solución potente y flexible. Permite crear documentos bien estructurados utilizando tecnologías web familiares como HTML y CSS. 🚀
Jorge García
Fullstack developer