PDFs com Spring-Boot

Recentemente eu percebi que nunca havia criado uma aplicação que gerasse pdf personalizados, então decidi criar um projeto para isso. 😁

O primeiro passo foi criar um projeto Spring Boot, e adicinar algumas dependências bem básicas.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency> 
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<scope>runtime</scope>
	<optional>true</optional>
</dependency>

IText

Pesquisando um pouco a primeira biblioteca que encontrei foi o iText, que é uma biblioteca Java para criar e manipular arquivos PDF.

Eu queria pode criar um arquivo em HTML e transformar ele em PDF, com isso eu utilizei o html2pdf um add-on do iText que permite renderizar HTML/CSS em PDF.

Adicionando a dependência no POM.xml

<!-- https://central.sonatype.com/artifact/com.itextpdf/html2pdf?smo=true -->
<dependency>
	<groupId>com.itextpdf</groupId>
	<artifactId>html2pdf</artifactId>
	<version>6.1.0</version>
</dependency>

Thymeleaf

Com isso eu precisava que os valores do pdf fossem dinâmicos, então eu adicionei o Thymeleaf para processar os templates HTML.

O Thymeleaf é um motor de templates para Java para gerar páginas HTML dinâmicas server side, ele permite integrar dados do backend diretamente no HTML usando atributos personalizados, como th:text, th:each e th:if.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Caso tiver algum problema em adicionar a dependências pom.xml

Criando um serviço para gerar PDFs

Com as dependências adicionadas, criei um serviço para gerar PDFs a partir de um arquivo HTML.

pdf-generator
├── services
│   └── PdfService.java

Na classe

Na classes eu utilizei o TemplateEngine para processar o template HTML e gerar o PDF com o HtmlConverter do html2pdf.

Para fins de exemplo eu estou utilizando dados hard code, mas você pode substituir por dados dinâmicos do seu banco de dados ou de uma API.

import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.io.ByteArrayOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

@Service
public class PdfService {

    private final TemplateEngine templateEngine;

    public PdfService(TemplateEngine templateEngine) {
        this.templateEngine = templateEngine;
    }

    public byte[] generatePdf(String templateName) {

        Context context = new Context();
        context.setVariable("dataHora", LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")));

        List<Map<String, String>> vendasMensais = List.of(
                Map.of("category", "Eletrônicos", "total", "R$ 90.000,00", "qtd", "90", "comission", "R$ 1.500,00"),
                Map.of("category", "Móveis", "total", "R$ 79.200,00", "qtd", "132", "comission", "R$ 1.320,00"),
                Map.of("category", "Vestuário", "total", "R$ 54.000,00", "qtd", "108", "comission", "R$ 900,00"),
                Map.of("category", "Acessórios", "total", "R$ 48.000,00", "qtd", "192", "comission", "R$ 800,00")
        );

        context.setVariable("itens", vendasMensais);

        // Processando o HTML passando os dados do contexto
        String html = templateEngine.process(templateName, context);

        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            ConverterProperties properties = new ConverterProperties();
            
            // Setando o diretório base para buscar os recursos (imagens, css, etc)
            properties.setBaseUri("src/main/resources/static/");
            HtmlConverter.convertToPdf(html, outputStream, properties);

            return outputStream.toByteArray();

        } catch (Exception e) {
            throw new RuntimeException("Erro ao gerar PDF", e);
        }
    }
}

Criando o controller

Em seguida eu criei o controller que será aonde será feita as requisições, e a onde será utilizado o serviço criado para gerar o PDF.

pdf-generator
├── service
│   └── PdfService.java
├── controller
│   └── PdfController.java

Na classe

Eu configurei o nome do relatório que será processado pelo Thymeleaf e convertido em PDF, e defini o nome do arquivo que será baixado.

import com.pdf_generator.services.PdfService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/pdf")
@RestController
public class PdfController {
    private final PdfService pdfService;

    public PdfController(PdfService pdfService) {
        this.pdfService = pdfService;
    }

    @GetMapping("/download")
    public ResponseEntity<byte[]> downloadPdf() {

            // Deve ser passado o mesmo nome do arquivo HTML que será processado
            byte[] pdfBytes = pdfService.generatePdf("report");
            HttpHeaders headers = new HttpHeaders();

            // Define o nome do arquivo que será baixado
            headers.add("Content-Disposition", "attachment; filename=Relatorio.pdf");

            return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
    }
}

Criando o template HTML

Agora vamos criar o template HTML que será processado pelo Thymeleaf e convertido em PDF, para conhecer mais sobre o Thymeleaf, acesse a documentação oficial.

pdf-generator
├── service
│   └── PdfService.java
├── controller
│   └── PdfController.java
├── resources
│   └── templates
│       └── relatorio.html

No arquivo

Devemos adicionar a tag xmlns:th="http://www.thymeleaf.org" no HTML para usar o Thymeleaf, e adicionar os atributos th:text e th:each para adicionar os valores dinâmicos no HTML.

<!DOCTYPE html>
<html lang="pt-BR">
<!-- Essa tag é necessária para usar o thymeleaf -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Relatório de Vendas</title>
</head>
<body>
<div>
    <div>
        <h2>Vendas por Categoria</h2>
        <table>
            <thead>
            <tr>
                <th>Categoria</th>
                <th>Quantidade</th>
                <th>Total Vendido</th>
                <th>Comissão</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="item : ${itens}">
                <td th:text="${item.category}"></td>
                <td th:text="${item.qtd}"></td>
                <td th:text="${item.total}"></td>
                <td th:text="${item.comission}"></td>
            </tr>
            </tbody>
        </table>
    </div>

    <footer>
        <p>Relatório gerado em <span th:text="${dataHora}"></span></p>
    </footer>
</div>
</body>
</html>

Você também pode adicionar imagens e CSS no HTML, basta adicionar o caminho relativo no atributo src da tag img e no atributo href da tag link.

Se tiver dúvidas sobre como adicionar imagens e CSS no HTML, você poder da uma olhada nesse commit.

Executando a aplicação

Para executar a aplicação, basta rodar o comando mvn spring-boot:run no terminal ou utilizar uma IDE como IntelliJ ou Eclipse.

Após a aplicação subir, acesse o endpoint http://localhost:8080/pdf/download e o PDF será baixado automaticamente.

No final o PDF gerado deve ficar parecido com isso: Relatório de Vendas Simples

Conclusão

O código completo você pode encontrar no repositório do projeto, e se tiver alguma dúvida ou sugestão você pode entrar em contato comigo pelo linkedin. 😊