Invoice Generation with Java: Complete Implementation Guide

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("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
}

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:

  1. Multiple output formats (PDF, HTML, XML, JSON)
  2. Professional templates with customizable styling
  3. Automatic calculations for taxes, discounts, and totals
  4. Flexible data models supporting complex invoice structures
  5. Spring Boot integration for web applications
  6. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper