Volver a la página principal
martes 21 enero 2025
11

Cómo generar PDFs con Flying Saucer y Thymeleaf en Spring Boot

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! 🚀

¿Qué es Flying Saucer?

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.

Requisitos previos

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.

1. Configuración del proyecto en Spring Boot

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'
}

2. Creando la plantilla HTML con Thymeleaf

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.

3. Servicio para la generación del PDF

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();
        }
    }
}

4. Controlador para la descarga del PDF

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);
        }
    }
}

5. Prueba de la aplicación

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.

Consejos adicionales

  • Usa CSS avanzado para mejorar el diseño del PDF.
  • Asegúrate de que el HTML sea válido XHTML, ya que Flying Saucer no admite HTML5 estándar.
  • Considera agregar imágenes y logotipos con rutas absolutas para que se rendericen correctamente.

Conclusión

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. 🚀

Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer