Skip to main content

Real-World Example

This page walks through an invoice template that demonstrates many of DocStencil's features.

The Scenario

A freelancer needs to generate invoices for clients. Each invoice includes:

  • Freelancer and customer addresses
  • Multiple line items in a table
  • Automatic calculation of subtotal, VAT, and total
  • Formatted dates and numbers

The Template

Download the template (Invoice_Template.docx)

Features Demonstrated

This template showcases several DocStencil features:

Nested Property Access

Access deeply nested data using dot notation:

{invoice.freelancer.address.street}

Variable Declarations

Store calculated values for reuse with var:

{var totalHours = invoice.positions | $map((p) => p.hours) | $sum()}

Variable declarations will not leave empty lines after being evaluated.

Pipe Operations

Chain multiple functions together using the pipe operator |:

{invoice.serviceName | $lowercase()}
{total | $format("0")}

The pipe operator adds the expression on the left side of the pipe as the first argument to the function call on the right. The following two expressions are equivalent:

{total | $format("0")}
{$format(total, "0")}

Lambda Expressions

Use arrow functions for simple data transformations:

{invoice.positions | $map((p) => p.hours * p.rate) | $sum()}

Case Expressions

Handle conditional logic inline:

{case when invoice.vatPercent != null then totalWithoutTax * invoice.vatPercent / 100.0 else 0 end}

Date Formatting

Format dates using Java's DateTimeFormatter patterns:

{invoice.date | $format("MMMM d yyyy")}

For Loops in Tables

Repeat table rows for each item in a list:

{for pos in invoice.positions}...{end}

For loops and if statements automatically wrap table rows if their corresponding {end} partner is in a different table cell.

The Data

import com.docstencil.core.api.OfficeTemplate
import java.time.LocalDate

// Data classes
data class InvoiceAddress(
val street: String,
val streetNumber: String,
val zipCode: String,
val city: String,
val country: String,
)

data class InvoicePerson(
val name: String,
val address: InvoiceAddress,
val uid: String,
val companyRegistrationNumber: String,
val iban: String,
)

data class InvoicePosition(
val name: String,
val hours: Int,
val rate: Int,
)

data class Invoice(
val id: String,
val serviceName: String,
val freelancer: InvoicePerson,
val customer: InvoicePerson,
val positions: List<InvoicePosition>,
val date: LocalDate,
val serviceFrom: LocalDate,
val serviceTo: LocalDate,
val vatPercent: Int,
)

fun main() {
val template = OfficeTemplate.fromFile("Invoice_Template.docx")

val data = mapOf(
"invoice" to Invoice(
id = "20250815",
serviceName = "Development services",
freelancer = InvoicePerson(
name = "Fred Freelancer",
address = InvoiceAddress(
street = "Stubenring",
streetNumber = "1",
zipCode = "1234",
city = "Vienna",
country = "Austria",
),
uid = "ATU12345678",
companyRegistrationNumber = "FN 123456",
iban = "AT123400000012345678",
),
customer = InvoicePerson(
name = "Big Corp Ltd.",
address = InvoiceAddress(
street = "Tauentzienstraße",
streetNumber = "42",
zipCode = "54321",
city = "Berlin",
country = "Germany",
),
uid = "DEU123456789",
companyRegistrationNumber = "HR 1234567",
iban = "DE123400000012345678",
),
positions = listOf(
InvoicePosition("Consulting", 10, 100),
InvoicePosition("Development", 50, 120),
),
date = LocalDate.of(2025, 8, 15),
serviceFrom = LocalDate.of(2025, 7, 19),
serviceTo = LocalDate.of(2025, 6, 16),
vatPercent = 20,
),
)

val result = template.render(data)
result.writeToFile("Invoice_Output.docx")
}

The Output

Download the output (Invoice_Output.docx)

The rendered invoice shows all template expressions replaced with actual values. Notice that {var ...} statements do not leave empty lines and the original styling is preserved throughout the document.