google-site-verification: google61fe8ba583a51912.html
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