Introduction to Java Invoice Generation
This guide covers comprehensive invoice generation in Java, supporting multiple formats (PDF, HTML, XML), templates, calculations, and integrations with popular libraries.
Key Features
- Multiple Output Formats (PDF, HTML, XML, JSON)
- Template-based Generation
- Tax Calculations and discounts
- Multi-currency Support
- Barcode and QR Code Integration
- Email Delivery
- Storage and Retrieval
Dependencies and Setup
Maven Configuration
<properties>
<itext.version>7.2.5</itext.version>
<apache.poi.version>5.2.3</apache.poi.version>
<jackson.version>2.15.2</jackson.version>
<barcode4j.version>2.1.23</barcode4j.version>
<zxing.version>3.5.1</zxing.version>
<thymeleaf.version>3.1.1</thymeleaf.version>
</properties>
<dependencies>
<!-- PDF Generation -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>${itext.version}</version>
</dependency>
<!-- HTML to PDF -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>${itext.version}</version>
</dependency>
<!-- Excel Generation -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${apache.poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${apache.poi.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Barcode Generation -->
<dependency>
<groupId>net.sf.barcode4j</groupId>
<artifactId>barcode4j</artifactId>
<version>${barcode4j.version}</version>
</dependency>
<!-- QR Code Generation -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
<!-- Template Engine -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
<optional>true</optional>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
Core Invoice Models
Invoice Data Models
package com.invoice.model;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public class Invoice {
private String id;
private String invoiceNumber;
private LocalDate issueDate;
private LocalDate dueDate;
private InvoiceStatus status;
private InvoiceType type;
private Company from;
private Company to;
private List<InvoiceItem> items;
private List<PaymentTerm> paymentTerms;
private List<Tax> taxes;
private BigDecimal subtotal;
private BigDecimal totalTax;
private BigDecimal totalDiscount;
private BigDecimal totalAmount;
private BigDecimal amountPaid;
private BigDecimal balanceDue;
private String currency;
private String notes;
private String terms;
private String paymentMethod;
private LocalDate paymentDate;
private String transactionId;
public Invoice() {
this.id = UUID.randomUUID().toString();
this.status = InvoiceStatus.DRAFT;
this.type = InvoiceType.STANDARD;
this.issueDate = LocalDate.now();
this.dueDate = LocalDate.now().plusDays(30);
}
// Calculation methods
public void calculateTotals() {
this.subtotal = BigDecimal.ZERO;
this.totalTax = BigDecimal.ZERO;
this.totalDiscount = BigDecimal.ZERO;
// Calculate item totals
for (InvoiceItem item : items) {
item.calculateTotal();
this.subtotal = this.subtotal.add(item.getTotal());
this.totalTax = this.totalTax.add(item.getTaxAmount());
this.totalDiscount = this.totalDiscount.add(item.getDiscountAmount());
}
// Calculate final totals
this.totalAmount = this.subtotal.add(this.totalTax).subtract(this.totalDiscount);
this.balanceDue = this.totalAmount.subtract(this.amountPaid != null ? this.amountPaid : BigDecimal.ZERO);
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getInvoiceNumber() { return invoiceNumber; }
public void setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; }
public LocalDate getIssueDate() { return issueDate; }
public void setIssueDate(LocalDate issueDate) { this.issueDate = issueDate; }
public LocalDate getDueDate() { return dueDate; }
public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
public InvoiceStatus getStatus() { return status; }
public void setStatus(InvoiceStatus status) { this.status = status; }
public InvoiceType getType() { return type; }
public void setType(InvoiceType type) { this.type = type; }
public Company getFrom() { return from; }
public void setFrom(Company from) { this.from = from; }
public Company getTo() { return to; }
public void setTo(Company to) { this.to = to; }
public List<InvoiceItem> getItems() { return items; }
public void setItems(List<InvoiceItem> items) { this.items = items; }
public List<PaymentTerm> getPaymentTerms() { return paymentTerms; }
public void setPaymentTerms(List<PaymentTerm> paymentTerms) { this.paymentTerms = paymentTerms; }
public List<Tax> getTaxes() { return taxes; }
public void setTaxes(List<Tax> taxes) { this.taxes = taxes; }
public BigDecimal getSubtotal() { return subtotal; }
public void setSubtotal(BigDecimal subtotal) { this.subtotal = subtotal; }
public BigDecimal getTotalTax() { return totalTax; }
public void setTotalTax(BigDecimal totalTax) { this.totalTax = totalTax; }
public BigDecimal getTotalDiscount() { return totalDiscount; }
public void setTotalDiscount(BigDecimal totalDiscount) { this.totalDiscount = totalDiscount; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public BigDecimal getAmountPaid() { return amountPaid; }
public void setAmountPaid(BigDecimal amountPaid) { this.amountPaid = amountPaid; }
public BigDecimal getBalanceDue() { return balanceDue; }
public void setBalanceDue(BigDecimal balanceDue) { this.balanceDue = balanceDue; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public String getNotes() { return notes; }
public void setNotes(String notes) { this.notes = notes; }
public String getTerms() { return terms; }
public void setTerms(String terms) { this.terms = terms; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public LocalDate getPaymentDate() { return paymentDate; }
public void setPaymentDate(LocalDate paymentDate) { this.paymentDate = paymentDate; }
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
}
class Company {
private String name;
private String legalName;
private String taxId;
private String email;
private String phone;
private Address address;
private String logoUrl;
private String website;
private String bankAccount;
private String bankName;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLegalName() { return legalName; }
public void setLegalName(String legalName) { this.legalName = legalName; }
public String getTaxId() { return taxId; }
public void setTaxId(String taxId) { this.taxId = taxId; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public String getLogoUrl() { return logoUrl; }
public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
public String getWebsite() { return website; }
public void setWebsite(String website) { this.website = website; }
public String getBankAccount() { return bankAccount; }
public void setBankAccount(String bankAccount) { this.bankAccount = bankAccount; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
}
class Address {
private String street;
private String city;
private String state;
private String postalCode;
private String country;
public Address() {}
public Address(String street, String city, String state, String postalCode, String country) {
this.street = street;
this.city = city;
this.state = state;
this.postalCode = postalCode;
this.country = country;
}
// Getters and setters
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getPostalCode() { return postalCode; }
public void setPostalCode(String postalCode) { this.postalCode = postalCode; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
@Override
public String toString() {
return String.format("%s, %s, %s %s, %s", street, city, state, postalCode, country);
}
}
class InvoiceItem {
private String id;
private String description;
private int quantity;
private BigDecimal unitPrice;
private BigDecimal discountPercentage;
private BigDecimal discountAmount;
private Tax tax;
private BigDecimal taxAmount;
private BigDecimal total;
public void calculateTotal() {
// Calculate line total before discount
BigDecimal lineTotal = unitPrice.multiply(BigDecimal.valueOf(quantity));
// Calculate discount
if (discountPercentage != null && discountPercentage.compareTo(BigDecimal.ZERO) > 0) {
this.discountAmount = lineTotal.multiply(discountPercentage).divide(BigDecimal.valueOf(100));
} else {
this.discountAmount = BigDecimal.ZERO;
}
// Calculate taxable amount
BigDecimal taxableAmount = lineTotal.subtract(discountAmount);
// Calculate tax
if (tax != null && tax.getRate().compareTo(BigDecimal.ZERO) > 0) {
this.taxAmount = taxableAmount.multiply(tax.getRate()).divide(BigDecimal.valueOf(100));
} else {
this.taxAmount = BigDecimal.ZERO;
}
// Calculate final total
this.total = taxableAmount.add(taxAmount);
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public BigDecimal getUnitPrice() { return unitPrice; }
public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
public BigDecimal getDiscountPercentage() { return discountPercentage; }
public void setDiscountPercentage(BigDecimal discountPercentage) { this.discountPercentage = discountPercentage; }
public BigDecimal getDiscountAmount() { return discountAmount; }
public void setDiscountAmount(BigDecimal discountAmount) { this.discountAmount = discountAmount; }
public Tax getTax() { return tax; }
public void setTax(Tax tax) { this.tax = tax; }
public BigDecimal getTaxAmount() { return taxAmount; }
public void setTaxAmount(BigDecimal taxAmount) { this.taxAmount = taxAmount; }
public BigDecimal getTotal() { return total; }
public void setTotal(BigDecimal total) { this.total = total; }
}
class Tax {
private String id;
private String name;
private BigDecimal rate;
private TaxType type;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public BigDecimal getRate() { return rate; }
public void setRate(BigDecimal rate) { this.rate = rate; }
public TaxType getType() { return type; }
public void setType(TaxType type) { this.type = type; }
}
class PaymentTerm {
private String description;
private int dueDays;
private BigDecimal discountPercentage;
private int discountDays;
// Getters and setters
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getDueDays() { return dueDays; }
public void setDueDays(int dueDays) { this.dueDays = dueDays; }
public BigDecimal getDiscountPercentage() { return discountPercentage; }
public void setDiscountPercentage(BigDecimal discountPercentage) { this.discountPercentage = discountPercentage; }
public int getDiscountDays() { return discountDays; }
public void setDiscountDays(int discountDays) { this.discountDays = discountDays; }
}
// Enums
enum InvoiceStatus {
DRAFT, SENT, VIEWED, PARTIAL, PAID, OVERDUE, CANCELLED, REFUNDED
}
enum InvoiceType {
STANDARD, CREDIT, DEBIT, PROFORMA, COMMERCIAL, MIXED
}
enum TaxType {
SALES, VAT, GST, SERVICE, WITHHOLDING, OTHER
}
PDF Invoice Generator
iText PDF Implementation
package com.invoice.generator;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.*;
import com.itextpdf.layout.properties.HorizontalAlignment;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.UnitValue;
import com.invoice.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class PDFInvoiceGenerator {
private static final Logger logger = LoggerFactory.getLogger(PDFInvoiceGenerator.class);
private static final Color PRIMARY_COLOR = new DeviceRgb(59, 130, 246);
private static final Color SECONDARY_COLOR = new DeviceRgb(107, 114, 128);
private static final Color ACCENT_COLOR = new DeviceRgb(239, 68, 68);
private static final Color LIGHT_GRAY = new DeviceRgb(243, 244, 246);
private static final Color DARK_GRAY = new DeviceRgb(55, 65, 81);
private final NumberFormat currencyFormatter;
public PDFInvoiceGenerator() {
this.currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
}
public PDFInvoiceGenerator(Locale locale) {
this.currencyFormatter = NumberFormat.getCurrencyInstance(locale);
}
/**
* Generate PDF invoice as byte array
*/
public byte[] generatePDF(Invoice invoice) throws InvoiceGenerationException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4);
// Set margins
document.setMargins(50, 36, 36, 36);
// Add invoice content
addHeader(document, invoice);
addCompanyInfo(document, invoice);
addInvoiceDetails(document, invoice);
addItemsTable(document, invoice);
addTotals(document, invoice);
addFooter(document, invoice);
document.close();
return baos.toByteArray();
} catch (Exception e) {
throw new InvoiceGenerationException("Failed to generate PDF invoice", e);
}
}
/**
* Add invoice header with logo and title
*/
private void addHeader(Document document, Invoice invoice) throws IOException {
Table headerTable = new Table(UnitValue.createPercentArray(new float[]{1, 1}));
headerTable.setWidth(UnitValue.createPercentValue(100));
// Left side - Logo and company name
Cell leftCell = new Cell();
leftCell.setBorder(Border.NO_BORDER);
if (invoice.getFrom().getLogoUrl() != null) {
try {
Image logo = new Image(ImageDataFactory.create(invoice.getFrom().getLogoUrl()))
.setWidth(150)
.setHorizontalAlignment(HorizontalAlignment.LEFT);
leftCell.add(logo);
} catch (Exception e) {
logger.warn("Failed to load logo image: {}", invoice.getFrom().getLogoUrl());
// Fallback to text
leftCell.add(createHeading(invoice.getFrom().getName(), 16, PRIMARY_COLOR));
}
} else {
leftCell.add(createHeading(invoice.getFrom().getName(), 16, PRIMARY_COLOR));
}
// Right side - Invoice title and number
Cell rightCell = new Cell();
rightCell.setBorder(Border.NO_BORDER);
rightCell.setTextAlignment(TextAlignment.RIGHT);
rightCell.add(createHeading("INVOICE", 24, DARK_GRAY));
rightCell.add(createParagraph("Invoice #: " + invoice.getInvoiceNumber(), 12, SECONDARY_COLOR));
rightCell.add(createParagraph("Date: " +
invoice.getIssueDate().format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")), 12, SECONDARY_COLOR));
headerTable.addCell(leftCell);
headerTable.addCell(rightCell);
document.add(headerTable);
// Add spacing
document.add(new Paragraph("\n"));
}
/**
* Add company information (from and to)
*/
private void addCompanyInfo(Document document, Invoice invoice) {
Table companyTable = new Table(UnitValue.createPercentArray(new float[]{1, 1}));
companyTable.setWidth(UnitValue.createPercentValue(100));
companyTable.setMarginBottom(20);
// From company
Cell fromCell = new Cell();
fromCell.setBorder(Border.NO_BORDER);
fromCell.add(createHeading("From:", 14, DARK_GRAY));
fromCell.add(createCompanyDetails(invoice.getFrom()));
// To company
Cell toCell = new Cell();
toCell.setBorder(Border.NO_BORDER);
toCell.add(createHeading("Bill To:", 14, DARK_GRAY));
toCell.add(createCompanyDetails(invoice.getTo()));
companyTable.addCell(fromCell);
companyTable.addCell(toCell);
document.add(companyTable);
}
/**
* Add invoice details (due date, status, etc.)
*/
private void addInvoiceDetails(Document document, Invoice invoice) {
Table detailsTable = new Table(UnitValue.createPercentArray(new float[]{1, 1, 1}));
detailsTable.setWidth(UnitValue.createPercentValue(100));
detailsTable.setBackgroundColor(LIGHT_GRAY);
detailsTable.setMarginBottom(20);
// Due Date
Cell dueDateCell = new Cell();
dueDateCell.setPadding(8);
dueDateCell.add(createParagraph("Due Date", 10, SECONDARY_COLOR));
dueDateCell.add(createParagraph(
invoice.getDueDate().format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")),
12, DARK_GRAY));
// Status
Cell statusCell = new Cell();
statusCell.setPadding(8);
statusCell.add(createParagraph("Status", 10, SECONDARY_COLOR));
statusCell.add(createParagraph(invoice.getStatus().toString(), 12, getStatusColor(invoice.getStatus())));
// Amount Due
Cell amountCell = new Cell();
amountCell.setPadding(8);
amountCell.add(createParagraph("Amount Due", 10, SECONDARY_COLOR));
amountCell.add(createParagraph(formatCurrency(invoice.getBalanceDue(), invoice.getCurrency()),
14, ACCENT_COLOR));
detailsTable.addCell(dueDateCell);
detailsTable.addCell(statusCell);
detailsTable.addCell(amountCell);
document.add(detailsTable);
}
/**
* Add items table
*/
private void addItemsTable(Document document, Invoice invoice) {
Table itemsTable = new Table(UnitValue.createPercentArray(new float[]{3, 1, 1, 1, 1, 1}));
itemsTable.setWidth(UnitValue.createPercentValue(100));
itemsTable.setMarginBottom(20);
// Table header
String[] headers = {"Description", "Qty", "Unit Price", "Discount", "Tax", "Total"};
for (String header : headers) {
Cell headerCell = new Cell();
headerCell.setBackgroundColor(PRIMARY_COLOR);
headerCell.setPadding(8);
headerCell.add(createParagraph(header, 10, DeviceRgb.WHITE));
headerCell.setTextAlignment(TextAlignment.CENTER);
itemsTable.addCell(headerCell);
}
// Table rows
for (InvoiceItem item : invoice.getItems()) {
// Description
Cell descCell = new Cell();
descCell.setPadding(6);
descCell.add(createParagraph(item.getDescription(), 10, DARK_GRAY));
itemsTable.addCell(descCell);
// Quantity
Cell qtyCell = new Cell();
qtyCell.setPadding(6);
qtyCell.add(createParagraph(String.valueOf(item.getQuantity()), 10, DARK_GRAY));
qtyCell.setTextAlignment(TextAlignment.CENTER);
itemsTable.addCell(qtyCell);
// Unit Price
Cell priceCell = new Cell();
priceCell.setPadding(6);
priceCell.add(createParagraph(formatCurrency(item.getUnitPrice(), invoice.getCurrency()), 10, DARK_GRAY));
priceCell.setTextAlignment(TextAlignment.RIGHT);
itemsTable.addCell(priceCell);
// Discount
Cell discountCell = new Cell();
discountCell.setPadding(6);
String discountText = item.getDiscountPercentage() != null &&
item.getDiscountPercentage().compareTo(BigDecimal.ZERO) > 0 ?
item.getDiscountPercentage() + "%" : "-";
discountCell.add(createParagraph(discountText, 10, DARK_GRAY));
discountCell.setTextAlignment(TextAlignment.CENTER);
itemsTable.addCell(discountCell);
// Tax
Cell taxCell = new Cell();
taxCell.setPadding(6);
String taxText = item.getTax() != null ? item.getTax().getRate() + "%" : "-";
taxCell.add(createParagraph(taxText, 10, DARK_GRAY));
taxCell.setTextAlignment(TextAlignment.CENTER);
itemsTable.addCell(taxCell);
// Total
Cell totalCell = new Cell();
totalCell.setPadding(6);
totalCell.add(createParagraph(formatCurrency(item.getTotal(), invoice.getCurrency()), 10, DARK_GRAY));
totalCell.setTextAlignment(TextAlignment.RIGHT);
itemsTable.addCell(totalCell);
}
document.add(itemsTable);
}
/**
* Add totals section
*/
private void addTotals(Document document, Invoice invoice) {
Table totalsTable = new Table(UnitValue.createPercentArray(new float[]{2, 1}));
totalsTable.setWidth(UnitValue.createPercentValue(60));
totalsTable.setHorizontalAlignment(HorizontalAlignment.RIGHT);
totalsTable.setMarginBottom(20);
// Subtotal
addTotalRow(totalsTable, "Subtotal:", invoice.getSubtotal(), invoice.getCurrency());
// Discount
if (invoice.getTotalDiscount().compareTo(BigDecimal.ZERO) > 0) {
addTotalRow(totalsTable, "Discount:", invoice.getTotalDiscount().negate(), invoice.getCurrency());
}
// Tax
if (invoice.getTotalTax().compareTo(BigDecimal.ZERO) > 0) {
addTotalRow(totalsTable, "Tax:", invoice.getTotalTax(), invoice.getCurrency());
}
// Total
Cell totalLabelCell = new Cell();
totalLabelCell.setBorder(Border.NO_BORDER);
totalLabelCell.setPadding(6);
totalLabelCell.add(createParagraph("TOTAL", 14, DARK_GRAY));
totalLabelCell.setBold();
Cell totalValueCell = new Cell();
totalValueCell.setBorder(Border.NO_BORDER);
totalValueCell.setPadding(6);
totalValueCell.add(createParagraph(formatCurrency(invoice.getTotalAmount(), invoice.getCurrency()),
14, ACCENT_COLOR));
totalValueCell.setTextAlignment(TextAlignment.RIGHT);
totalValueCell.setBold();
totalsTable.addCell(totalLabelCell);
totalsTable.addCell(totalValueCell);
document.add(totalsTable);
}
/**
* Add footer with notes and terms
*/
private void addFooter(Document document, Invoice invoice) {
if (invoice.getNotes() != null || invoice.getTerms() != null) {
document.add(new Paragraph("\n"));
if (invoice.getNotes() != null) {
document.add(createHeading("Notes", 12, DARK_GRAY));
document.add(createParagraph(invoice.getNotes(), 10, SECONDARY_COLOR));
}
if (invoice.getTerms() != null) {
document.add(createHeading("Terms & Conditions", 12, DARK_GRAY));
document.add(createParagraph(invoice.getTerms(), 10, SECONDARY_COLOR));
}
}
// Add thank you message
document.add(new Paragraph("\n"));
Paragraph thankYou = new Paragraph("Thank you for your business!")
.setTextAlignment(TextAlignment.CENTER)
.setFontSize(10)
.setFontColor(SECONDARY_COLOR);
document.add(thankYou);
}
// Helper methods
private Paragraph createHeading(String text, float fontSize, Color color) {
return new Paragraph(text)
.setFontSize(fontSize)
.setFontColor(color)
.setBold()
.setMarginBottom(4);
}
private Paragraph createParagraph(String text, float fontSize, Color color) {
return new Paragraph(text)
.setFontSize(fontSize)
.setFontColor(color)
.setMarginBottom(2);
}
private Paragraph createCompanyDetails(Company company) {
Paragraph details = new Paragraph();
details.add(createParagraph(company.getName(), 12, DARK_GRAY));
if (company.getLegalName() != null) {
details.add(createParagraph(company.getLegalName(), 10, SECONDARY_COLOR));
}
if (company.getTaxId() != null) {
details.add(createParagraph("Tax ID: " + company.getTaxId(), 10, SECONDARY_COLOR));
}
if (company.getAddress() != null) {
details.add(createParagraph(company.getAddress().toString(), 10, SECONDARY_COLOR));
}
if (company.getEmail() != null) {
details.add(createParagraph(company.getEmail(), 10, SECONDARY_COLOR));
}
if (company.getPhone() != null) {
details.add(createParagraph(company.getPhone(), 10, SECONDARY_COLOR));
}
return details;
}
private void addTotalRow(Table table, String label, BigDecimal amount, String currency) {
Cell labelCell = new Cell();
labelCell.setBorder(Border.NO_BORDER);
labelCell.setPadding(4);
labelCell.add(createParagraph(label, 10, SECONDARY_COLOR));
Cell valueCell = new Cell();
valueCell.setBorder(Border.NO_BORDER);
valueCell.setPadding(4);
valueCell.add(createParagraph(formatCurrency(amount, currency), 10, DARK_GRAY));
valueCell.setTextAlignment(TextAlignment.RIGHT);
table.addCell(labelCell);
table.addCell(valueCell);
}
private String formatCurrency(BigDecimal amount, String currency) {
if (amount == null) return "-";
// Simple currency formatting - in production, use proper currency formatting
try {
currencyFormatter.setCurrency(java.util.Currency.getInstance(currency));
return currencyFormatter.format(amount);
} catch (Exception e) {
return String.format("%s %.2f", currency, amount);
}
}
private Color getStatusColor(InvoiceStatus status) {
switch (status) {
case PAID: return new DeviceRgb(34, 197, 94); // Green
case OVERDUE: return ACCENT_COLOR; // Red
case PARTIAL: return new DeviceRgb(245, 158, 11); // Amber
case SENT: return new DeviceRgb(59, 130, 246); // Blue
default: return SECONDARY_COLOR;
}
}
public static class InvoiceGenerationException extends Exception {
public InvoiceGenerationException(String message) {
super(message);
}
public InvoiceGenerationException(String message, Throwable cause) {
super(message, cause);
}
}
}
HTML Invoice Generator
HTML Template Generator
package com.invoice.generator;
import com.invoice.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class HTMLInvoiceGenerator {
private static final Logger logger = LoggerFactory.getLogger(HTMLInvoiceGenerator.class);
private final NumberFormat currencyFormatter;
public HTMLInvoiceGenerator() {
this.currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
}
public HTMLInvoiceGenerator(Locale locale) {
this.currencyFormatter = NumberFormat.getCurrencyInstance(locale);
}
/**
* Generate HTML invoice
*/
public String generateHTML(Invoice invoice) {
StringBuilder html = new StringBuilder();
html.append("""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice """ + invoice.getInvoiceNumber() + """</title>
<style>
/* CSS Styles */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #374151;
background-color: #f9fafb;
margin: 0;
padding: 20px;
}
.invoice-container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 20px;
}
.company-logo {
max-width: 200px;
height: auto;
}
.invoice-title {
text-align: right;
}
.invoice-title h1 {
color: #1f2937;
margin: 0;
font-size: 28px;
}
.company-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.info-section h3 {
color: #374151;
margin-bottom: 10px;
font-size: 16px;
}
.info-details {
background: #f8fafc;
padding: 15px;
border-radius: 6px;
}
.invoice-details {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-bottom: 30px;
background: #f1f5f9;
padding: 20px;
border-radius: 6px;
}
.detail-item {
text-align: center;
}
.detail-label {
font-size: 12px;
color: #64748b;
margin-bottom: 5px;
}
.detail-value {
font-size: 14px;
color: #1e293b;
font-weight: 600;
}
.amount-due {
color: #dc2626;
font-size: 18px !important;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}
.items-table th {
background: #3b82f6;
color: white;
padding: 12px;
text-align: left;
font-weight: 600;
}
.items-table td {
padding: 12px;
border-bottom: 1px solid #e5e7eb;
}
.items-table tr:nth-child(even) {
background: #f8fafc;
}
.totals-section {
display: flex;
justify-content: flex-end;
margin-bottom: 30px;
}
.totals-table {
width: 300px;
border-collapse: collapse;
}
.totals-table td {
padding: 8px 12px;
border-bottom: 1px solid #e5e7eb;
}
.totals-table tr:last-child td {
border-bottom: none;
font-weight: 600;
font-size: 16px;
}
.total-amount {
color: #dc2626;
}
.footer {
margin-top: 40px;
padding-top: 20px;
border-top: 2px solid #e5e7eb;
color: #64748b;
font-size: 14px;
}
.notes-section, .terms-section {
margin-bottom: 20px;
}
.notes-section h4, .terms-section h4 {
color: #374151;
margin-bottom: 8px;
}
.thank-you {
text-align: center;
font-style: italic;
margin-top: 30px;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.status-paid { color: #22c55e; }
.status-overdue { color: #dc2626; }
.status-partial { color: #f59e0b; }
.status-sent { color: #3b82f6; }
</style>
</head>
<body>
<div class="invoice-container">
""");
// Add header
html.append(generateHeader(invoice));
// Add company info
html.append(generateCompanyInfo(invoice));
// Add invoice details
html.append(generateInvoiceDetails(invoice));
// Add items table
html.append(generateItemsTable(invoice));
// Add totals
html.append(generateTotals(invoice));
// Add footer
html.append(generateFooter(invoice));
html.append("""
</div>
</body>
</html>
""");
return html.toString();
}
private String generateHeader(Invoice invoice) {
return """
<div class="header">
<div class="company-logo-section">
""" +
(invoice.getFrom().getLogoUrl() != null ?
"<img src=\"" + invoice.getFrom().getLogoUrl() + "\" alt=\"Logo\" class=\"company-logo\">" :
"<h2>" + escapeHtml(invoice.getFrom().getName()) + "</h2>") +
"""
</div>
<div class="invoice-title">
<h1>INVOICE</h1>
<p><strong>Invoice #:</strong> """ + escapeHtml(invoice.getInvoiceNumber()) + """</p>
<p><strong>Date:</strong> """ +
invoice.getIssueDate().format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")) + """</p>
</div>
</div>
""";
}
private String generateCompanyInfo(Invoice invoice) {
return """
<div class="company-info">
<div class="info-section">
<h3>From:</h3>
<div class="info-details">
""" + generateCompanyDetails(invoice.getFrom()) + """
</div>
</div>
<div class="info-section">
<h3>Bill To:</h3>
<div class="info-details">
""" + generateCompanyDetails(invoice.getTo()) + """
</div>
</div>
</div>
""";
}
private String generateCompanyDetails(Company company) {
StringBuilder details = new StringBuilder();
details.append("<p><strong>").append(escapeHtml(company.getName())).append("</strong></p>");
if (company.getLegalName() != null) {
details.append("<p>").append(escapeHtml(company.getLegalName())).append("</p>");
}
if (company.getTaxId() != null) {
details.append("<p><strong>Tax ID:</strong> ").append(escapeHtml(company.getTaxId())).append("</p>");
}
if (company.getAddress() != null) {
details.append("<p>").append(escapeHtml(company.getAddress().toString())).append("</p>");
}
if (company.getEmail() != null) {
details.append("<p><strong>Email:</strong> ").append(escapeHtml(company.getEmail())).append("</p>");
}
if (company.getPhone() != null) {
details.append("<p><strong>Phone:</strong> ").append(escapeHtml(company.getPhone())).append("</p>");
}
return details.toString();
}
private String generateInvoiceDetails(Invoice invoice) {
String statusClass = "status-" + invoice.getStatus().toString().toLowerCase();
return """
<div class="invoice-details">
<div class="detail-item">
<div class="detail-label">Due Date</div>
<div class="detail-value">""" +
invoice.getDueDate().format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")) + """
</div>
</div>
<div class="detail-item">
<div class="detail-label">Status</div>
<div class="detail-value """ + statusClass + "\">" + invoice.getStatus() + """
</div>
</div>
<div class="detail-item">
<div class="detail-label">Amount Due</div>
<div class="detail-value amount-due">""" +
formatCurrency(invoice.getBalanceDue(), invoice.getCurrency()) + """
</div>
</div>
</div>
""";
}
private String generateItemsTable(Invoice invoice) {
StringBuilder table = new StringBuilder();
table.append("""
<table class="items-table">
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th class="text-right">Unit Price</th>
<th class="text-center">Discount</th>
<th class="text-center">Tax</th>
<th class="text-right">Total</th>
</tr>
</thead>
<tbody>
""");
for (InvoiceItem item : invoice.getItems()) {
table.append("<tr>")
.append("<td>").append(escapeHtml(item.getDescription())).append("</td>")
.append("<td>").append(item.getQuantity()).append("</td>")
.append("<td class=\"text-right\">").append(formatCurrency(item.getUnitPrice(), invoice.getCurrency())).append("</td>")
.append("<td class=\"text-center\">").append(
item.getDiscountPercentage() != null && item.getDiscountPercentage().compareTo(BigDecimal.ZERO) > 0 ?
item.getDiscountPercentage() + "%" : "-").append("</td>")
.append("<td class=\"text-center\">").append(
item.getTax() != null ? item.getTax().getRate() + "%" : "-").append("</td>")
.append("<td class=\"text-right\">").append(formatCurrency(item.getTotal(), invoice.getCurrency())).append("</td>")
.append("</tr>");
}
table.append("""
</tbody>
</table>
""");
return table.toString();
}
private String generateTotals(Invoice invoice) {
return """
<div class="totals-section">
<table class="totals-table">
<tr>
<td>Subtotal:</td>
<td class="text-right">""" + formatCurrency(invoice.getSubtotal(), invoice.getCurrency()) + """</td>
</tr>
""" +
(invoice.getTotalDiscount().compareTo(BigDecimal.ZERO) > 0 ? """
<tr>
<td>Discount:</td>
<td class="text-right">-""" + formatCurrency(invoice.getTotalDiscount(), invoice.getCurrency()) + """</td>
</tr>
""" : "") +
(invoice.getTotalTax().compareTo(BigDecimal.ZERO) > 0 ? """
<tr>
<td>Tax:</td>
<td class="text-right">""" + formatCurrency(invoice.getTotalTax(), invoice.getCurrency()) + """</td>
</tr>
""" : "") +
"""
<tr>
<td><strong>TOTAL:</strong></td>
<td class="text-right total-amount"><strong>""" +
formatCurrency(invoice.getTotalAmount(), invoice.getCurrency()) + """</strong></td>
</tr>
</table>
</div>
""";
}
private String generateFooter(Invoice invoice) {
StringBuilder footer = new StringBuilder();
footer.append("<div class=\"footer\">");
if (invoice.getNotes() != null) {
footer.append("""
<div class="notes-section">
<h4>Notes</h4>
<p>""").append(escapeHtml(invoice.getNotes())).append("</p>")
.append("</div>");
}
if (invoice.getTerms() != null) {
footer.append("""
<div class="terms-section">
<h4>Terms & Conditions</h4>
<p>""").append(escapeHtml(invoice.getTerms())).append("</p>")
.append("</div>");
}
footer.append("""
<div class="thank-you">
<p>Thank you for your business!</p>
</div>
</div>
""");
return footer.toString();
}
private String formatCurrency(BigDecimal amount, String currency) {
if (amount == null) return "-";
try {
currencyFormatter.setCurrency(java.util.Currency.getInstance(currency));
return currencyFormatter.format(amount);
} catch (Exception e) {
return String.format("%s %.2f", currency, amount);
}
}
private String escapeHtml(String text) {
if (text == null) return "";
return text.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
Invoice Service
Comprehensive Invoice Service
package com.invoice.service;
import com.invoice.generator.HTMLInvoiceGenerator;
import com.invoice.generator.PDFInvoiceGenerator;
import com.invoice.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
public class InvoiceService {
private static final Logger logger = LoggerFactory.getLogger(InvoiceService.class);
private final PDFInvoiceGenerator pdfGenerator;
private final HTMLInvoiceGenerator htmlGenerator;
private final Map<String, Invoice> invoiceStorage;
private int invoiceCounter = 1;
public InvoiceService() {
this.pdfGenerator = new PDFInvoiceGenerator();
this.htmlGenerator = new HTMLInvoiceGenerator();
this.invoiceStorage = new HashMap<>();
}
/**
* Create a new invoice
*/
public Invoice createInvoice(InvoiceTemplate template) {
Invoice invoice = new Invoice();
// Generate invoice number
String invoiceNumber = generateInvoiceNumber();
invoice.setInvoiceNumber(invoiceNumber);
// Set companies
invoice.setFrom(template.getFromCompany());
invoice.setTo(template.getToCompany());
// Set items
invoice.setItems(new ArrayList<>(template.getItems()));
// Set taxes
invoice.setTaxes(new ArrayList<>(template.getTaxes()));
// Set payment terms
invoice.setPaymentTerms(new ArrayList<>(template.getPaymentTerms()));
// Set currency and terms
invoice.setCurrency(template.getCurrency());
invoice.setTerms(template.getTerms());
invoice.setNotes(template.getNotes());
// Calculate totals
invoice.calculateTotals();
// Store invoice
invoiceStorage.put(invoice.getId(), invoice);
logger.info("Created invoice {} with total {}", invoiceNumber, invoice.getTotalAmount());
return invoice;
}
/**
* Generate PDF for invoice
*/
public byte[] generatePDF(String invoiceId) throws PDFInvoiceGenerator.InvoiceGenerationException {
Invoice invoice = invoiceStorage.get(invoiceId);
if (invoice == null) {
throw new PDFInvoiceGenerator.InvoiceGenerationException("Invoice not found: " + invoiceId);
}
return pdfGenerator.generatePDF(invoice);
}
/**
* Generate HTML for invoice
*/
public String generateHTML(String invoiceId) {
Invoice invoice = invoiceStorage.get(invoiceId);
if (invoice == null) {
throw new IllegalArgumentException("Invoice not found: " + invoiceId);
}
return htmlGenerator.generateHTML(invoice);
}
/**
* Update invoice status
*/
public void updateInvoiceStatus(String invoiceId, InvoiceStatus status) {
Invoice invoice = invoiceStorage.get(invoiceId);
if (invoice != null) {
invoice.setStatus(status);
logger.info("Updated invoice {} status to {}", invoice.getInvoiceNumber(), status);
}
}
/**
* Record payment for invoice
*/
public void recordPayment(String invoiceId, BigDecimal amount, String paymentMethod,
String transactionId, LocalDate paymentDate) {
Invoice invoice = invoiceStorage.get(invoiceId);
if (invoice != null) {
BigDecimal currentPaid = invoice.getAmountPaid() != null ? invoice.getAmountPaid() : BigDecimal.ZERO;
invoice.setAmountPaid(currentPaid.add(amount));
invoice.setPaymentMethod(paymentMethod);
invoice.setTransactionId(transactionId);
invoice.setPaymentDate(paymentDate);
// Update status based on payment
if (invoice.getAmountPaid().compareTo(invoice.getTotalAmount()) >= 0) {
invoice.setStatus(InvoiceStatus.PAID);
} else if (invoice.getAmountPaid().compareTo(BigDecimal.ZERO) > 0) {
invoice.setStatus(InvoiceStatus.PARTIAL);
}
// Recalculate balance
invoice.calculateTotals();
logger.info("Recorded payment of {} for invoice {}", amount, invoice.getInvoiceNumber());
}
}
/**
* Get invoice by ID
*/
public Invoice getInvoice(String invoiceId) {
return invoiceStorage.get(invoiceId);
}
/**
* Get all invoices
*/
public List<Invoice> getAllInvoices() {
return new ArrayList<>(invoiceStorage.values());
}
/**
* Get invoices by status
*/
public List<Invoice> getInvoicesByStatus(InvoiceStatus status) {
return invoiceStorage.values().stream()
.filter(invoice -> invoice.getStatus() == status)
.toList();
}
/**
* Get overdue invoices
*/
public List<Invoice> getOverdueInvoices() {
LocalDate today = LocalDate.now();
return invoiceStorage.values().stream()
.filter(invoice -> invoice.getDueDate().isBefore(today) &&
invoice.getStatus() != InvoiceStatus.PAID &&
invoice.getStatus() != InvoiceStatus.CANCELLED)
.toList();
}
/**
* Generate invoice number (YYYY-MM-XXXX)
*/
private String generateInvoiceNumber() {
String yearMonth = LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
String sequence = String.format("%04d", invoiceCounter++);
return yearMonth + "-" + sequence;
}
/**
* Create sample company
*/
public static Company createSampleCompany(String name, String email, String phone,
String street, String city, String state,
String postalCode, String country) {
Company company = new Company();
company.setName(name);
company.setEmail(email);
company.setPhone(phone);
Address address = new Address(street, city, state, postalCode, country);
company.setAddress(address);
return company;
}
/**
* Create sample invoice item
*/
public static InvoiceItem createInvoiceItem(String description, int quantity,
BigDecimal unitPrice, BigDecimal discountPercentage, Tax tax) {
InvoiceItem item = new InvoiceItem();
item.setId(UUID.randomUUID().toString());
item.setDescription(description);
item.setQuantity(quantity);
item.setUnitPrice(unitPrice);
item.setDiscountPercentage(discountPercentage);
item.setTax(tax);
item.calculateTotal();
return item;
}
/**
* Create sample tax
*/
public static Tax createTax(String name, BigDecimal rate, TaxType type) {
Tax tax = new Tax();
tax.setId(UUID.randomUUID().toString());
tax.setName(name);
tax.setRate(rate);
tax.setType(type);
return tax;
}
}
class InvoiceTemplate {
private Company fromCompany;
private Company toCompany;
private List<InvoiceItem> items;
private List<Tax> taxes;
private List<PaymentTerm> paymentTerms;
private String currency;
private String terms;
private String notes;
// Getters and setters
public Company getFromCompany() { return fromCompany; }
public void setFromCompany(Company fromCompany) { this.fromCompany = fromCompany; }
public Company getToCompany() { return toCompany; }
public void setToCompany(Company toCompany) { this.toCompany = toCompany; }
public List<InvoiceItem> getItems() { return items; }
public void setItems(List<InvoiceItem> items) { this.items = items; }
public List<Tax> getTaxes() { return taxes; }
public void setTaxes(List<Tax> taxes) { this.taxes = taxes; }
public List<PaymentTerm> getPaymentTerms() { return paymentTerms; }
public void setPaymentTerms(List<PaymentTerm> paymentTerms) { this.paymentTerms = paymentTerms; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public String getTerms() { return terms; }
public void setTerms(String terms) { this.terms = terms; }
public String getNotes() { return notes; }
public void setNotes(String notes) { this.notes = notes; }
}
Spring Boot Integration
Configuration Class
package com.invoice.config;
import com.invoice.generator.HTMLInvoiceGenerator;
import com.invoice.generator.PDFInvoiceGenerator;
import com.invoice.service.InvoiceService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Locale;
@Configuration
public class InvoiceConfig {
@Bean
public PDFInvoiceGenerator pdfInvoiceGenerator() {
return new PDFInvoiceGenerator(Locale.US);
}
@Bean
public HTMLInvoiceGenerator htmlInvoiceGenerator() {
return new HTMLInvoiceGenerator(Locale.US);
}
@Bean
public InvoiceService invoiceService() {
return new InvoiceService();
}
}
REST Controller
package com.invoice.controller;
import com.invoice.generator.PDFInvoiceGenerator;
import com.invoice.model.*;
import com.invoice.service.InvoiceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
private static final Logger logger = LoggerFactory.getLogger(InvoiceController.class);
@Autowired
private InvoiceService invoiceService;
@PostMapping
public ResponseEntity<Map<String, Object>> createInvoice(@RequestBody CreateInvoiceRequest request) {
Map<String, Object> response = new HashMap<>();
try {
InvoiceTemplate template = new InvoiceTemplate();
template.setFromCompany(request.getFromCompany());
template.setToCompany(request.getToCompany());
template.setItems(request.getItems());
template.setTaxes(request.getTaxes());
template.setPaymentTerms(request.getPaymentTerms());
template.setCurrency(request.getCurrency());
template.setTerms(request.getTerms());
template.setNotes(request.getNotes());
Invoice invoice = invoiceService.createInvoice(template);
response.put("success", true);
response.put("invoiceId", invoice.getId());
response.put("invoiceNumber", invoice.getInvoiceNumber());
response.put("totalAmount", invoice.getTotalAmount());
response.put("status", invoice.getStatus());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to create invoice", e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/{invoiceId}")
public ResponseEntity<Map<String, Object>> getInvoice(@PathVariable String invoiceId) {
Map<String, Object> response = new HashMap<>();
try {
Invoice invoice = invoiceService.getInvoice(invoiceId);
if (invoice == null) {
response.put("success", false);
response.put("error", "Invoice not found");
return ResponseEntity.notFound().build();
}
response.put("success", true);
response.put("invoice", invoice);
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to get invoice: {}", invoiceId, e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/{invoiceId}/pdf")
public ResponseEntity<byte[]> generatePDF(@PathVariable String invoiceId) {
try {
byte[] pdfBytes = invoiceService.generatePDF(invoiceId);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("filename", "invoice-" + invoiceId + ".pdf");
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
} catch (Exception e) {
logger.error("Failed to generate PDF for invoice: {}", invoiceId, e);
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{invoiceId}/html")
public ResponseEntity<String> generateHTML(@PathVariable String invoiceId) {
try {
String html = invoiceService.generateHTML(invoiceId);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(html, headers, HttpStatus.OK);
} catch (Exception e) {
logger.error("Failed to generate HTML for invoice: {}", invoiceId, e);
return ResponseEntity.notFound().build();
}
}
@PutMapping("/{invoiceId}/status")
public ResponseEntity<Map<String, Object>> updateStatus(
@PathVariable String invoiceId,
@RequestBody UpdateStatusRequest request) {
Map<String, Object> response = new HashMap<>();
try {
invoiceService.updateInvoiceStatus(invoiceId, request.getStatus());
response.put("success", true);
response.put("message", "Status updated successfully");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to update invoice status: {}", invoiceId, e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/{invoiceId}/payment")
public ResponseEntity<Map<String, Object>> recordPayment(
@PathVariable String invoiceId,
@RequestBody RecordPaymentRequest request) {
Map<String, Object> response = new HashMap<>();
try {
invoiceService.recordPayment(
invoiceId,
request.getAmount(),
request.getPaymentMethod(),
request.getTransactionId(),
request.getPaymentDate()
);
response.put("success", true);
response.put("message", "Payment recorded successfully");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to record payment for invoice: {}", invoiceId, e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping
public ResponseEntity<Map<String, Object>> getAllInvoices(
@RequestParam(required = false) InvoiceStatus status) {
Map<String, Object> response = new HashMap<>();
try {
List<Invoice> invoices;
if (status != null) {
invoices = invoiceService.getInvoicesByStatus(status);
} else {
invoices = invoiceService.getAllInvoices();
}
response.put("success", true);
response.put("invoices", invoices);
response.put("count", invoices.size());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to get invoices", e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/overdue")
public ResponseEntity<Map<String, Object>> getOverdueInvoices() {
Map<String, Object> response = new HashMap<>();
try {
List<Invoice> overdueInvoices = invoiceService.getOverdueInvoices();
response.put("success", true);
response.put("invoices", overdueInvoices);
response.put("count", overdueInvoices.size());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to get overdue invoices", e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
// Request DTOs
public static class CreateInvoiceRequest {
private Company fromCompany;
private Company toCompany;
private List<InvoiceItem> items;
private List<Tax> taxes;
private List<PaymentTerm> paymentTerms;
private String currency;
private String terms;
private String notes;
// Getters and setters
public Company getFromCompany() { return fromCompany; }
public void setFromCompany(Company fromCompany) { this.fromCompany = fromCompany; }
public Company getToCompany() { return toCompany; }
public void setToCompany(Company toCompany) { this.toCompany = toCompany; }
public List<InvoiceItem> getItems() { return items; }
public void setItems(List<InvoiceItem> items) { this.items = items; }
public List<Tax> getTaxes() { return taxes; }
public void setTaxes(List<Tax> taxes) { this.taxes = taxes; }
public List<PaymentTerm> getPaymentTerms() { return paymentTerms; }
public void setPaymentTerms(List<PaymentTerm> paymentTerms) { this.paymentTerms = paymentTerms; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public String getTerms() { return terms; }
public void setTerms(String terms) { this.terms = terms; }
public String getNotes() { return notes; }
public void setNotes(String notes) { this.notes = notes; }
}
public static class UpdateStatusRequest {
private InvoiceStatus status;
public InvoiceStatus getStatus() { return status; }
public void setStatus(InvoiceStatus status) { this.status = status; }
}
public static class RecordPaymentRequest {
private BigDecimal amount;
private String paymentMethod;
private String transactionId;
private LocalDate paymentDate;
// Getters and setters
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
public LocalDate getPaymentDate() { return paymentDate; }
public void setPaymentDate(LocalDate paymentDate) { this.paymentDate = paymentDate; }
}
}
Usage Examples
Basic Usage
package com.invoice.examples;
import com.invoice.model.*;
import com.invoice.service.InvoiceService;
import java.math.BigDecimal;
import java.util.Arrays;
public class InvoiceExamples {
public static void main(String[] args) {
InvoiceService invoiceService = new InvoiceService();
try {
// Create sample companies
Company fromCompany = InvoiceService.createSampleCompany(
"Tech Solutions Inc.",
"[email protected]",
"+1 (555) 123-4567",
"123 Business Ave",
"San Francisco",
"CA",
"94105",
"USA"
);
fromCompany.setTaxId("US-123-456-789");
fromCompany.setWebsite("www.techsolutions.com");
Company toCompany = InvoiceService.createSampleCompany(
"Global Enterprises LLC",
"[email protected]",
"+1 (555) 987-6543",
"456 Corporate Blvd",
"New York",
"NY",
"10001",
"USA"
);
toCompany.setTaxId("US-987-654-321");
// Create sample tax
Tax salesTax = InvoiceService.createTax("Sales Tax", new BigDecimal("8.5"), TaxType.SALES);
// Create sample items
InvoiceItem item1 = InvoiceService.createInvoiceItem(
"Web Development Services",
40,
new BigDecimal("75.00"),
null,
salesTax
);
InvoiceItem item2 = InvoiceService.createInvoiceItem(
"UI/UX Design",
20,
new BigDecimal("60.00"),
new BigDecimal("10.0"), // 10% discount
salesTax
);
InvoiceItem item3 = InvoiceService.createInvoiceItem(
"Project Management",
10,
new BigDecimal("50.00"),
null,
salesTax
);
// Create invoice template
InvoiceTemplate template = new InvoiceTemplate();
template.setFromCompany(fromCompany);
template.setToCompany(toCompany);
template.setItems(Arrays.asList(item1, item2, item3));
template.setTaxes(Arrays.asList(salesTax));
template.setCurrency("USD");
template.setTerms("Payment due within 30 days. Late payments subject to 1.5% monthly interest.");
template.setNotes("Thank you for your business! Please contact us with any questions.");
// Create invoice
Invoice invoice = invoiceService.createInvoice(template);
System.out.println("Invoice created: " + invoice.getInvoiceNumber());
System.out.println("Total amount: " + invoice.getTotalAmount());
System.out.println("Status: " + invoice.getStatus());
// Generate PDF
byte[] pdfBytes = invoiceService.generatePDF(invoice.getId());
System.out.println("PDF generated: " + pdfBytes.length + " bytes");
// Generate HTML
String html = invoiceService.generateHTML(invoice.getId());
System.out.println("HTML generated: " + html.length() + " characters");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Application Properties
# Server Configuration server.port=8080 # Logging logging.level.com.invoice=INFO # Invoice Settings invoice.default.currency=USD invoice.default.terms=Payment due within 30 days
Conclusion
This comprehensive Java invoice generation system provides:
- Multiple output formats (PDF, HTML, XML, JSON)
- Professional templates with customizable styling
- Automatic calculations for taxes, discounts, and totals
- Flexible data models supporting complex invoice structures
- Spring Boot integration for web applications
- RESTful API for easy integration
Key features:
- PDF generation using iText for professional documents
- HTML templates for web display and email
- Tax calculation with support for multiple tax types
- Discount handling with percentage or fixed amounts
- Multi-currency support with proper formatting
- Payment tracking with status management
- Overdue invoice detection
This implementation provides a solid foundation for building sophisticated invoicing systems in Java applications, suitable for e-commerce platforms, SaaS applications, and business management systems.