Generating professional invoices is a critical requirement for many business applications. iText, a powerful Java PDF library, provides the tools needed to create sophisticated, compliant, and branded invoice documents programmatically. This guide explores practical patterns for generating dynamic invoices with iText, incorporating best practices for layout, styling, and data integration.
Understanding iText PDF Generation
iText offers two main approaches for PDF creation:
- Low-level API: Direct content stream manipulation for maximum control
- High-level API: Document abstraction with elements and styles
- XML/HTML Worker: Convert HTML to PDF (useful for existing templates)
Core Invoice Generation Patterns
1. Project Setup and Dependencies
Configure iText dependencies in your build system.
Maven Configuration:
<dependencies> <!-- iText Core --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-core</artifactId> <version>8.0.2</version> <type>pom</type> </dependency> <!-- Layout module for high-level API --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>layout</artifactId> <version>8.0.2</version> </dependency> <!-- PDF/A support for archival --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>pdfa</artifactId> <version>8.0.2</version> </dependency> </dependencies>
2. Domain Models for Invoice Data
Create comprehensive Java models to represent invoice data.
Invoice Domain Models:
@Data
public class Invoice {
private String invoiceNumber;
private LocalDate issueDate;
private LocalDate dueDate;
private Company from;
private Company to;
private List<InvoiceItem> items;
private BigDecimal subtotal;
private BigDecimal taxAmount;
private BigDecimal total;
private String currency;
private String paymentTerms;
private String notes;
public BigDecimal calculateSubtotal() {
return items.stream()
.map(InvoiceItem::getLineTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public BigDecimal calculateTax(BigDecimal taxRate) {
return calculateSubtotal().multiply(taxRate);
}
}
@Data
public class Company {
private String name;
private String address;
private String city;
private String state;
private String zipCode;
private String country;
private String phone;
private String email;
private String logoPath;
}
@Data
public class InvoiceItem {
private String description;
private int quantity;
private BigDecimal unitPrice;
private BigDecimal taxRate;
public BigDecimal getLineTotal() {
return unitPrice.multiply(BigDecimal.valueOf(quantity));
}
public BigDecimal getLineTax() {
return getLineTotal().multiply(taxRate);
}
}
3. Basic Invoice Generator Service
Create a service class for PDF invoice generation.
Core Invoice Service:
@Service
public class InvoicePdfService {
private static final Font TITLE_FONT = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18);
private static final Font HEADER_FONT = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12);
private static final Font NORMAL_FONT = FontFactory.getFont(FontFactory.HELVETICA, 10);
private static final Font BOLD_FONT = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 10);
public byte[] generateInvoice(Invoice invoice) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4);
// Set document metadata
pdfDoc.getDocumentInfo()
.setTitle("Invoice " + invoice.getInvoiceNumber())
.setAuthor(invoice.getFrom().getName())
.setCreator("Invoice System v1.0");
// Build invoice content
buildInvoiceHeader(document, invoice);
buildCompanyInfo(document, invoice);
buildInvoiceItems(document, invoice);
buildTotals(document, invoice);
buildFooter(document, invoice);
document.close();
return baos.toByteArray();
}
}
private void buildInvoiceHeader(Document document, Invoice invoice) {
Paragraph header = new Paragraph()
.add(new Text("INVOICE").setFont(TITLE_FONT))
.add(new Text("\nNumber: " + invoice.getInvoiceNumber()).setFont(BOLD_FONT))
.add(new Text("\nIssue Date: " + invoice.getIssueDate()).setFont(NORMAL_FONT))
.add(new Text("\nDue Date: " + invoice.getDueDate()).setFont(NORMAL_FONT))
.setTextAlignment(TextAlignment.RIGHT);
document.add(header);
document.add(new Paragraph("\n"));
}
private void buildCompanyInfo(Document document, Invoice invoice) {
float[] columnWidths = {1, 1};
Table companyTable = new Table(columnWidths);
// From company
Cell fromCell = new Cell();
fromCell.add(new Paragraph("From:").setFont(HEADER_FONT));
fromCell.add(buildCompanyParagraph(invoice.getFrom()));
// To company
Cell toCell = new Cell();
toCell.add(new Paragraph("Bill To:").setFont(HEADER_FONT));
toCell.add(buildCompanyParagraph(invoice.getTo()));
companyTable.addCell(fromCell);
companyTable.addCell(toCell);
document.add(companyTable);
document.add(new Paragraph("\n"));
}
private Paragraph buildCompanyParagraph(Company company) {
return new Paragraph()
.add(company.getName() + "\n")
.add(company.getAddress() + "\n")
.add(company.getCity() + ", " + company.getState() + " " + company.getZipCode() + "\n")
.add(company.getCountry() + "\n")
.add("Phone: " + company.getPhone() + "\n")
.add("Email: " + company.getEmail())
.setFont(NORMAL_FONT);
}
}
4. Advanced Invoice Items Table
Create a detailed table for invoice line items.
Invoice Items Table:
private void buildInvoiceItems(Document document, Invoice invoice) {
float[] columnWidths = {3, 1, 1, 1, 1};
Table itemsTable = new Table(columnWidths);
itemsTable.setWidth(UnitValue.createPercentValue(100));
// Table header
itemsTable.addHeaderCell(createHeaderCell("Description"));
itemsTable.addHeaderCell(createHeaderCell("Qty"));
itemsTable.addHeaderCell(createHeaderCell("Unit Price"));
itemsTable.addHeaderCell(createHeaderCell("Tax Rate"));
itemsTable.addHeaderCell(createHeaderCell("Total"));
// Table rows
for (InvoiceItem item : invoice.getItems()) {
itemsTable.addCell(createCell(item.getDescription()));
itemsTable.addCell(createCell(String.valueOf(item.getQuantity())));
itemsTable.addCell(createCell(formatCurrency(item.getUnitPrice(), invoice.getCurrency())));
itemsTable.addCell(createCell(formatPercentage(item.getTaxRate())));
itemsTable.addCell(createCell(formatCurrency(item.getLineTotal(), invoice.getCurrency())));
}
document.add(itemsTable);
document.add(new Paragraph("\n"));
}
private Cell createHeaderCell(String text) {
return new Cell()
.add(new Paragraph(text).setFont(BOLD_FONT))
.setBackgroundColor(new DeviceGray(0.8f))
.setTextAlignment(TextAlignment.CENTER);
}
private Cell createCell(String text) {
return new Cell()
.add(new Paragraph(text).setFont(NORMAL_FONT))
.setPadding(5);
}
5. Totals and Summary Section
Calculate and display invoice totals.
Totals Section:
private void buildTotals(Document document, Invoice invoice) {
float[] columnWidths = {3, 1};
Table totalsTable = new Table(columnWidths);
totalsTable.setWidth(UnitValue.createPercentValue(50));
totalsTable.setHorizontalAlignment(HorizontalAlignment.RIGHT);
// Subtotal
totalsTable.addCell(createCell("Subtotal:"));
totalsTable.addCell(createCell(formatCurrency(invoice.getSubtotal(), invoice.getCurrency())));
// Tax
totalsTable.addCell(createCell("Tax:"));
totalsTable.addCell(createCell(formatCurrency(invoice.getTaxAmount(), invoice.getCurrency())));
// Total
totalsTable.addCell(createCell("TOTAL:").setFont(BOLD_FONT));
totalsTable.addCell(createCell(formatCurrency(invoice.getTotal(), invoice.getCurrency())).setFont(BOLD_FONT));
document.add(totalsTable);
}
private String formatCurrency(BigDecimal amount, String currency) {
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
if ("EUR".equals(currency)) {
format = NumberFormat.getCurrencyInstance(Locale.GERMANY);
}
return format.format(amount);
}
private String formatPercentage(BigDecimal rate) {
NumberFormat format = NumberFormat.getPercentInstance();
format.setMinimumFractionDigits(2);
return format.format(rate);
}
6. Styling and Branding Enhancements
Add logos, colors, and custom styling.
Styled Invoice Generator:
@Component
public class StyledInvoiceService {
public byte[] generateStyledInvoice(Invoice invoice) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc);
// Add company logo
addLogo(document, invoice.getFrom().getLogoPath());
// Create styled content
buildStyledHeader(document, invoice);
buildStyledItems(document, invoice);
document.close();
return baos.toByteArray();
}
}
private void addLogo(Document document, String logoPath) throws MalformedURLException {
if (logoPath != null && !logoPath.isEmpty()) {
ImageData imageData = ImageDataFactory.create(logoPath);
Image logo = new Image(imageData);
logo.setWidth(100);
logo.setHorizontalAlignment(HorizontalAlignment.LEFT);
document.add(logo);
}
}
private void buildStyledHeader(Document document, Invoice invoice) {
Div headerDiv = new Div();
headerDiv.setBackgroundColor(new DeviceRgb(240, 240, 240));
headerDiv.setPadding(10);
Paragraph title = new Paragraph("TAX INVOICE")
.setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD))
.setFontSize(16)
.setFontColor(new DeviceRgb(0, 0, 128));
headerDiv.add(title);
document.add(headerDiv);
}
}
7. PDF/A Compliance for Archival
Generate archival-quality PDF invoices.
PDF/A Compliant Invoice:
@Service
public class PdfAInvoiceService {
public byte[] generatePdfAInvoice(Invoice invoice) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PdfWriter writer = new PdfWriter(baos);
PdfADocument pdfDoc = new PdfADocument(
writer,
PdfAConformanceLevel.PDF_A_3A,
new PdfOutputIntent("Custom", "", "http://www.color.org",
"sRGB IEC61966-2.1",
new FileInputStream("sRGB_CS_profile.icm"))
);
Document document = new Document(pdfDoc);
// PDF/A requires specific metadata
pdfDoc.getDocumentInfo()
.setTitle("Invoice " + invoice.getInvoiceNumber())
.setAuthor(invoice.getFrom().getName())
.setSubject("Commercial Invoice")
.setKeywords("invoice, billing, commercial")
.setCreator("Invoice System v1.0")
.setCreationDate(new PdfDate(Calendar.getInstance()));
// Build invoice content (same as regular PDF)
buildInvoiceContent(document, invoice);
document.close();
return baos.toByteArray();
}
}
}
8. REST API for Invoice Generation
Expose invoice generation as a web service.
Invoice Controller:
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
private final InvoicePdfService invoicePdfService;
public InvoiceController(InvoicePdfService invoicePdfService) {
this.invoicePdfService = invoicePdfService;
}
@PostMapping("/generate")
public ResponseEntity<byte[]> generateInvoice(@RequestBody Invoice invoice) {
try {
byte[] pdfBytes = invoicePdfService.generateInvoice(invoice);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/pdf")
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=invoice-" + invoice.getInvoiceNumber() + ".pdf")
.body(pdfBytes);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/preview/{invoiceNumber}")
public ResponseEntity<byte[]> previewInvoice(@PathVariable String invoiceNumber) {
try {
Invoice invoice = invoiceService.getInvoiceByNumber(invoiceNumber);
byte[] pdfBytes = invoicePdfService.generateInvoice(invoice);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/pdf")
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=preview.pdf")
.body(pdfBytes);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}
Best Practices for PDF Invoice Generation
- Font Embedding: Always embed fonts to ensure consistent rendering across systems
- Error Handling: Implement robust error handling for PDF generation failures
- Memory Management: Use try-with-resources for proper resource cleanup
- Template Reuse: Consider using XML or HTML templates for complex layouts
- Performance: Cache frequently used elements like logos and headers
- Compliance: Ensure invoices meet legal requirements for your jurisdiction
- Testing: Generate test invoices with various data lengths and special characters
Conclusion: Professional Document Automation
iText provides Java developers with enterprise-grade capabilities for generating professional invoice documents. By combining iText's powerful layout engine with well-structured Java domain models, you can create dynamic, branded invoices that meet business requirements and compliance standards.
This approach transforms raw billing data into polished, professional documents—demonstrating that automated invoice generation doesn't mean compromising on quality or presentation. With iText and Java, you can deliver invoice experiences that reflect your brand's professionalism while streamlining your billing operations.