How to generate massive PDFs | Java |Spring
Introduction
In the development of enterprise applications, there’s often a need to generate multiple PDFs, such as invoices for multiple clients. In this article, we’ll explore how to create an API in Spring Boot that can generate multiple PDFs from a list of information. We’ll cover everything from project setup to code implementation, using popular libraries like iText for PDF generation, why itext? because in my opinion it is the easiest to use and most complete library to generate pdfs.
Step 1
Setting Up the Spring Boot Project Let’s start by creating a new Spring Boot project using Spring Initializr or your preferred IDE. Make sure to include the necessary dependencies, such as spring-boot-starter-web
for creating the REST API and itext7-core
the last version for PDF generation.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext7-core -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>8.0.3</version>
<type>pom</type>
</dependency>
Step 2
Designing the Model Class Define a model class to represent the data for each invoice. For example:
public class Invoice {
private String invoiceNumber;
private String clientName;
private LocalDate date;
private List<String> details;
private String titleImg;
//others methods
}
Step 3
Creating the REST Controller Create a REST controller to handle requests for generating the PDFs:
import com.massive.pdfs.dtos.Invoice;
import com.massive.pdfs.service.PDFGenerator;
import java.io.IOException;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PDFController {
private final PDFGenerator pdfGenerator;
public PDFController(PDFGenerator pdfGenerator) {
this.pdfGenerator = pdfGenerator;
}
@PostMapping("/generate-massive-pdfs")
public ResponseEntity<byte[]> generateMultiplePDFs(@RequestBody List<Invoice> invoices) {
try {
byte[] zipContent = pdfGenerator.generatePDFs(invoices);
return ResponseEntity.ok().header("Content-Disposition", "attachment; filename=invoices.zip")
.body(zipContent);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
}
Step 4
Implementing the PDF Generation Logic Implement the logic to generate the PDFs using the iText library or any other PDF generation library of your choice. You can use the generatePDF
method in the controller to generate a PDF for each invoice.
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.TextAlignment;
import com.massive.pdfs.dtos.Invoice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.springframework.stereotype.Component;
@Component
public class PDFGenerator {
private byte[] generatePDF(Invoice invoice) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(writer);
Document document = new Document(pdfDocument)) {
addTitleText(document, "Dinamic ttitle invoice 2024");
addTitleImage(document, invoice.getTitleImg(), true);
addInvoiceTable(document, invoice);
}
return outputStream.toByteArray();
}
private void addTitleImage(Document document, String titleImage, boolean isExternalImg) {
Image image = null;
try {
if (!isExternalImg)
image = new Image(ImageDataFactory.create(titleImage));
else
image = new Image(ImageDataFactory.create(new URL(titleImage)));
image.setWidth(100);
image.setTextAlignment(TextAlignment.LEFT);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Error to get img");
}
}
private void addTitleText(Document document, String titleText) {
Paragraph paragraph = new Paragraph(titleText).setBold().setFontSize(20).setMarginTop(20);
paragraph.setTextAlignment(TextAlignment.CENTER);
document.add(paragraph);
}
private void addInvoiceTable(Document document, Invoice invoice) {
Table table = new Table(2);
table.addCell("Invoice Number");
table.addCell(invoice.getInvoiceNumber());
table.addCell("Client Name");
table.addCell(invoice.getClientName());
table.addCell("Date");
table.addCell(invoice.getDate().toString());
table.addCell("Details");
com.itextpdf.layout.element.List list=new com.itextpdf.layout.element.List();
invoice.getDetails().forEach(list::add);
table.addCell(list);
document.add(table);
}
}
Step 5
Compressing the PDFs into a ZIP File Once the PDFs are generated, compress them into a ZIP file and return it as the response to the client. The client can then download the ZIP file containing all the generated PDFs.
public byte[] generatePDFs(List<Invoice> invoices) throws IOException {
ByteArrayOutputStream zipStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipStream)) {
for (int i = 0; i < invoices.size(); i++) {
Invoice invoice = invoices.get(i);
byte[] pdfContent = generatePDF(invoice);
zipOutputStream.putNextEntry(new ZipEntry("invoice_" + (i + 1) + ".pdf"));
zipOutputStream.write(pdfContent);
zipOutputStream.closeEntry();
}
}
return zipStream.toByteArray();
}
Step 6
Testing and Debugging Test the API thoroughly to ensure it works as expected. Send requests with different sets of invoice data and verify that the ZIP file contains the correct PDFs.
Sample of generation 10 pdfs files with simple desing
Here are some points to consider to evaluate if the time is acceptable:
Content complexity: If the PDFs contain a large amount of data, images, or complex graphics, the generation time could be longer. In this case, 3 seconds to generate 10 PDFs could be a reasonable time.
System resources: The generation time of the PDFs may be influenced by the processing capacity of your system. If you are running the PDF generation on hardware with limited resources, such as a server with low CPU power, the generation time could be longer.
Performance requirements: If your application requires the PDFs to be generated quickly to meet user or system performance requirements, you may need to optimize the PDF generation process to reduce processing time.
The following is an improvement to the method that generates the zip file using the available processors
public byte[] generatePDFsIn(List<Invoice> invoices) throws IOException, InterruptedException {
ByteArrayOutputStream zipStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipStream)) {
for (int i = 0; i < invoices.size(); i++) {
Invoice invoice = invoices.get(i);
@SuppressWarnings("unused")
ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
final int index = i;
executorService.execute(() -> {
try {
byte[] pdfContent = generatePDF(invoice);
synchronized (zipOutputStream) {
zipOutputStream.putNextEntry(new ZipEntry("invoice_" + (index + 1) + ".pdf"));
zipOutputStream.write(pdfContent);
zipOutputStream.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
return zipStream.toByteArray();
}
Thank you very much for reading I hope this helps you if you like to support me with a coffee it is always appreciated below I will leave the link to the Github repository
☕️ https://www.buymeacoffee.com/danielangel?new=1
🔨https://github.com/danielangel22/generatemassive-pdfs-springrest