Skip to content

Energy letter through all four APIs

This example recreates a business letter with a logo, recipient panel, Portuguese content, a price comparison table, a bottom-of-last-page signature and an image footer. All names, addresses, identifiers, websites, contact details and artwork are fictional.

Download the complete runnable Maven project. It includes all four inputs, the Java factories and shared example content, the rendering entry point, synthetic images, placeholder data and licensed fonts. No library implementation source or test framework is required.

Each PDF below was generated and checked by its own integration-test route. The Java source shown is the authoring factory, with shared example content/assets provided in the ZIP. EnergyExamples in that project configures fonts and runs all four routes. The HTML example intentionally uses semantic heading tags and need not produce byte-identical PDF data to the typed route.

Energy letter using Fluent Java: first page of the generated letter
Energy letter using Fluent Java Open PDF ↗
View complete Fluent Java input
package com.nsdev.lettergenerator.fixtures;
import com.nsdev.lettergenerator.Letter;
import com.nsdev.lettergenerator.Table;
import com.nsdev.lettergenerator.model.LetterDocument;
import com.nsdev.lettergenerator.model.block.ImageBlock;
import com.nsdev.lettergenerator.model.block.Paragraph;
import com.nsdev.lettergenerator.model.layout.SignaturePlacement;
import com.nsdev.lettergenerator.model.style.Style;
import com.nsdev.lettergenerator.model.style.TextAlign;
import com.nsdev.lettergenerator.model.text.TextRun;
import com.nsdev.lettergenerator.signature.TextSignature;
import java.io.IOException;
import java.util.List;
/**
* Fluent reference example: content calls and layout options, without fixed-zone records or HTML.
*/
public final class FluentEnergyLetter {
private FluentEnergyLetter() {}
public static LetterDocument document() throws IOException {
return Letter.builder()
.marginsMm(10)
.style(
Style.builder()
.fontFamily(EnergyLetterContent.FONT_FAMILY)
.fontSizePt(9)
.color("#222222")
.lineHeight(1.2)
.spaceAfterMm(2)
.build())
.logo(new ImageBlock(EnergyReferenceLetter.logo(), "image/png", 55d, 22d, null))
.header(EnergyLetterContent.RECIPIENT)
.headerBoundsMm(110, 20, 80, 35)
.headerAlignment(TextAlign.RIGHT)
.headerStyle(Style.builder().fontSizePt(8).build())
.body(EnergyLetterContent.SALUTATION)
.heading(EnergyLetterContent.TITLE)
.body(
EnergyLetterContent.REDUCTION,
EnergyLetterContent.DISCOUNT,
EnergyLetterContent.TARIFFS,
EnergyLetterContent.DISCOUNTS,
EnergyLetterContent.CANCELLATION)
.heading(EnergyLetterContent.COMPONENT_TITLE)
.body(EnergyLetterContent.POWER)
.table(
Table.builder(
EnergyLetterContent.COMPONENT_HEADER,
EnergyLetterContent.OLD_PRICE_HEADER,
EnergyLetterContent.NEW_PRICE_HEADER)
.headerStyle(
Style.builder()
.fontSizePt(8)
.color("#98BC6A")
.bold(false)
.textAlign(TextAlign.CENTER)
.build())
.style(
Style.builder()
.fontSizePt(8.5)
.lineHeight(1.15)
.spaceAfterMm(2)
.build())
.row("Energia (€/kWh)", "0,2008", "0,1632")
.row("Potência (€/dia)", "0,0645", "0,0815")
.build())
.body(EnergyLetterContent.NOTE, EnergyLetterContent.DELIVERY_POINT)
.heading(EnergyLetterContent.SUPPORT_TITLE)
.body(EnergyLetterContent.SUPPORT)
.signaturePlacement(SignaturePlacement.BOTTOM_LAST)
.signatureHeightMm(25)
.signature(
TextSignature.of(EnergyLetterContent.CLOSING)
.style(
Style.builder()
.fontSizePt(8)
.spaceBeforeMm(0)
.spaceAfterMm(1)
.build()),
TextSignature.of(
EnergyLetterContent.SIGNER_INSET
+ EnergyLetterContent.SIGNER)
.style(
Style.builder()
.fontFamily("Times")
.fontSizePt(16)
.italic()
.lineHeight(1.1)
.spaceBeforeMm(0)
.spaceAfterMm(1)
.build()),
TextSignature.of(EnergyLetterContent.SIGNER_DETAILS)
.style(
Style.builder()
.fontSizePt(8)
.spaceBeforeMm(0)
.spaceAfterMm(0)
.build()))
.footerBlocks(
new ImageBlock(
EnergyReferenceLetter.banner(),
"image/png",
190d,
5d,
Style.builder()
.textAlign(TextAlign.CENTER)
.spaceBeforeMm(0)
.spaceAfterMm(0)
.build()),
new Paragraph(
List.of(new TextRun(EnergyLetterContent.FOOTER, null, null)),
Style.builder()
.fontSizePt(6)
.color("#666666")
.textAlign(TextAlign.CENTER)
.spaceBeforeMm(1)
.spaceAfterMm(0)
.build()))
.footerHeightMm(14)
.build();
}
}

The letter uses A4 paper, 10 mm margins and an explicitly registered ReferenceSans font alias. Ordinary copy is 9 pt with controlled line height/spacing, fitting the complete example onto one page. DejaVu regular/bold files in the download supply its character coverage; the italic display signature uses standard Times.

The example’s font registration is explicit. Applications should package fonts appropriate to their required languages and licensing. The no-argument generator does not automatically discover these example fonts.

A separate logo sits at the top. The recipient header uses an exact 110 × 20 mm origin and an 80 × 35 mm box relative to the inner-page origin. Content alignment is right-aligned independently of that physical rectangle. The builder reserves enough body space for the final geometry.

The footer combines a named image with fictional legal text. Increase reserved heights when adding content; changing font size or spacing can change what fits. See header placement and footer content.

The Java routes explicitly assemble paragraphs and headings; the fluent route groups text naturally with body calls. JSON describes the immutable document graph. HTML supplies the same content using supported tags and literal data bindings.

The table has three equally weighted columns, a styled repeating header and two price rows. Header text color and background are configured separately from body typography. For more complex documents, rich table cells and spans support blocks, padding, borders and row keeps.

These are the complete literal values used by the HTML route:

energy-values.json
{
"customerName": "CLIENTE EXEMPLO",
"addressLine": "MORADA FICTÍCIA, 00",
"postalCode": "0000-000 LOCALIDADE",
"year": "2024",
"contractNumber": "DEMO-000001",
"effectiveDate": "1 de janeiro de 2024",
"previousDate": "31 de dezembro de 2023",
"discountPercentage": "15",
"contractPower": "3,45",
"previousEnergyPrice": "0,2008",
"energyPrice": "0,1632",
"previousPowerPrice": "0,0645",
"powerPrice": "0,0815",
"deliveryPoint": "DEMO-PT-00000000",
"contactNumber": "000 000 000",
"directorName": "RESPONSÁVEL EXEMPLO",
"directorRole": "Direção de Apoio",
"companyName": "Energia Exemplo",
"website": "energia.example"
}

The standalone runner supplies them as a Java map and registers company-logo and footer-banner byte assets. ${key} replacement is literal and map-only; the library does not infer a database or application persistence model. Use typed values with param when formatting should depend on the chosen country.

The signature selects BOTTOM_LAST / placement="bottom-last" with a 25 mm reservation. It occupies the final generated letter page above the footer. If body content expands, the configured policy still places the signature on the accepted final letter page; impossible geometry can fail instead of silently shrinking content.

This is a visual text signature, not cryptographic PDF signing. If an appendix is attached, the signature remains on the last letter page rather than the appendix.

In the extracted example project, after making the library artifact available:

Terminal window
mvn package dependency:copy-dependencies
java -Djava.awt.headless=true -cp 'target/classes:target/dependency/*' com.nsdev.lettergenerator.examples.EnergyExamples

Windows uses ; between classpath entries. The four files appear under output/ as energy-typed.pdf, energy-fluent.pdf, energy-json.pdf and energy-html.pdf.

This is also a useful starting point for changing a feature one at a time: try first/continuation stationery, a shared theme, localized dates or render diagnostics.