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.