GraphCompose
Build production PDF documents in Java without calculating coordinates.
Compose content in reading order, let GraphCompose handle layout and pagination,
then protect important templates with layout snapshots and visual diffs.
Release status —
🟢 Latest stable: v2.4.1 — a much larger template line-up: two new families, receipt and rota, new invoice, proposal and CV presets, and every CV preset marked ATS-friendly or design-first after three resume parsers read it; plus real letter spacing that keeps tracked caps searchable, one continuous timeline rail, and list items with hanging indents and drawn markers. See CHANGELOG.md.
· 🟡 In development: v2.4.2 on develop — see CHANGELOG.md.
Live showcase
·
Examples gallery
What it is
GraphCompose is a code-first document layout library for Java. You describe the
document — paragraphs, sections, lists, tables, images — and the engine measures,
wraps, places, and paginates it.
- Higher-level than PDFBox: no manual
x/y calculations for normal document flow.
- Lighter than a reporting platform: typed Java, no XML templates or datasource language.
- Built for change: deterministic geometry snapshots and pixel-level PDF diffs can
catch an invoice, CV, or report drifting before the change reaches production.
Use it for server-side invoices, proposals, CVs, reports, statements, schedules,
and other documents assembled from application data.
Outputs and templates
- PDF — the main production output, included in
graph-compose.
- PPTX — editable fixed-layout export, currently beta, provided by the
separate
graph-compose-render-pptx module.
- DOCX — semantic export with partial capability coverage, not full PDF parity,
provided by the separate
graph-compose-render-docx
module.
- Templates — 58 maintained designs across six document families in the separate
graph-compose-templates module: invoice, proposal, receipt and rota for business
documents, CV and cover letter for profiles. Start with
invoice and proposal,
CV and cover letter, or the
templates overview for all six families.
Choose a starting point
Create your first PDF
You need one dependency and one Java file. You do not need to understand the engine,
backends, templates, or repository modules first.
1. Add GraphCompose
Maven:
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>2.4.1</version>
</dependency>
Gradle:
dependencies { implementation("io.github.demchaav:graph-compose:2.4.1") }
The graph-compose artifact already includes PDF output.
2. Copy this complete file
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import java.nio.file.Path;
class Hello {
public static void main(String[] args) throws Exception {
try (DocumentSession document = GraphCompose.document(Path.of("hello.pdf"))
.margin(48, 48, 48, 48)
.create()) {
document.pageFlow(page -> page
.addParagraph("Hello GraphCompose")
.addParagraph("This PDF was created without manual coordinates."));
document.buildPdf();
}
}
}
Run Hello.main(). The file hello.pdf appears in the working directory.
That program contains the whole basic model:
GraphCompose.document(...) → pageFlow(...) → content blocks → buildPdf()
GraphCompose.document(...) chooses the output and page settings.
pageFlow(...) is ordinary top-to-bottom content.
- Block methods add content in reading order; the engine handles wrapping and page breaks.
buildPdf() writes the result.
Styling is optional. Introduce DocumentTextStyle after the document structure says
what it needs to say.
Grow it into a real document
A larger document uses the same flow. Group related content into named content
modules — module(...), a titled block of the document, not a Maven module — and
feed them your application data; coordinates still do not enter the authoring code.
document.pageFlow(page -> page
.module("Summary", module -> module.paragraph(summary))
.module("Skills", module -> module.bullets(skills))
.module("Projects", module -> module.rows(projects)));
The same flow accepts the common building blocks directly. You do not need a new
document model for each feature — a labelled bar chart is one more block in pageFlow:
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.chart.ChartData;
import com.demcha.compose.document.chart.ChartSpec;
import com.demcha.compose.document.chart.ValueLabelMode;
import java.nio.file.Path;
ChartData revenue = ChartData.builder()
.categories("Q1", "Q2", "Q3", "Q4")
.series("Revenue", 12.4, 15.1, 9.8, 14.2)
.build();
try (DocumentSession document = GraphCompose.document(Path.of("revenue.pdf")).create()) {
document.pageFlow(page -> page
.addParagraph("Quarterly revenue")
.chart(ChartSpec.bar()
.data(revenue)
.valueLabels(ValueLabelMode.OUTSIDE)
.build()));
document.buildPdf();
}
From there, the chart recipe shows the next questions in
order: value formatting, legends, bar/line shape, colours, grid, labels, background,
and snapshot coverage.
Keep production documents from drifting
Rendering successfully is the first check. Important documents should also prove that
their layout did not change unexpectedly after a library upgrade or a template edit.
Add the testing artifact at test scope:
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-testing</artifactId>
<version>2.4.1</version>
<scope>test</scope>
</dependency>
Then pin the resolved geometry in a normal JUnit test:
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.testing.layout.LayoutSnapshotAssertions;
import org.junit.jupiter.api.Test;
class InvoiceLayoutTest {
@Test
void layoutDoesNotDrift() throws Exception {
try (DocumentSession document = GraphCompose.document().create()) {
document.pageFlow(page -> page
.addParagraph("Invoice")
.addParagraph("Total: £125.00"));
LayoutSnapshotAssertions.assertMatches(document, "invoices/standard");
}
}
}
Create the first JSON baseline intentionally, commit it with the test, and run the test
normally in CI. If a node moves, a page break changes, or content order drifts, the test
fails with a reviewable geometry diff. CI never needs to update the baseline itself.
For flagship templates, add a second, pixel-level gate with PdfVisualRegression.
It renders the PDF pages to images and writes .actual.png and .diff.png artifacts on
mismatch, catching font, colour, glyph, and renderer changes that geometry alone cannot.
Start with Testing your document. The
layout snapshot and
visual regression pages are the deeper
references when you need custom baseline paths, approval flow, or cross-platform tolerance.
What to read next
Pick the one row that matches what you are doing. Each is a complete route — you do
not need the others, and you never need the engine internals to author a document.
| I want to… |
Go to |
| Understand the model behind the example above |
Your first document — a guided five-minute build |
| Add a feature — table, list, chart, timeline, image, header, footer, barcode, page-break rule |
Recipes — the complete task index |
| Start from a ready-made design |
Invoice and proposal · CV and cover letter · receipt, rota, and all 58 presets |
| Prove an upgrade did not move my document |
Testing your document — layout snapshots and visual diffs |
| Render from a server |
Production rendering — streaming, concurrency, failure handling; the Spring Boot streaming example is a working endpoint |
Contributing to GraphCompose itself is a separate path: start at
Contributing. The documentation index is a
reference catalogue — useful for finding a page, never required reading.
Output formats and optional modules
PDF is the production, fixed-layout output: graph-compose
includes the graph-compose-render-pdf backend.
graph-compose-bundle includes PDF, templates, fonts, and emoji. PPTX and DOCX
are not included in the bundle; add their render modules separately when needed.
Modules use the same GraphCompose version. Bundled fonts and
colour emoji have their own release lines. Consult the
module guide only when you need to split
dependencies, and the
backend capability matrix before
relying on a feature outside PDF.
Upgrading from 1.x? Read the 2.0 migration guide.
Scope and comparison
GraphCompose uses PDFBox as its PDF renderer. The difference is the authoring layer:
GraphCompose provides semantic document flow and automatic pagination, while PDFBox
provides low-level PDF primitives and direct document manipulation.
GraphCompose is an embedded Java library, not a hosted rendering service, WYSIWYG
editor, HTML/CSS renderer, or datasource-driven reporting engine.
| Library |
Authoring model |
Best fit |
| GraphCompose |
Typed Java document flow with deterministic layout tests |
Code-first business documents |
| PDFBox |
Low-level text and path primitives |
Direct PDF manipulation, parsing, extraction |
| iText 7 |
Object layout API plus low-level canvas |
Teams comfortable with AGPL or a commercial licence |
| JasperReports |
XML templates and datasource bindings |
Traditional tabular reporting |
Architecture for contributors
Application code authors against GraphCompose.document(...), DocumentSession, and
the semantic document DSL. Internally, GraphCompose compiles that node tree into a
deterministic layout, paginates it, and passes the result to a render backend.
flowchart LR
A["Java application"] --> B["DocumentSession + semantic DSL"]
B --> C["Document node tree"]
C --> D["measure → paginate → place"]
D --> E["PDF / PPTX backend"]
C -.-> F["semantic DOCX backend"]
D -.-> G["layout snapshot"]
Read the architecture overview,
package map, and
extension guide before changing engine or
backend code. The repository module map and build workflow live in
Contributing.
Project links
Documentation · Examples ·
Roadmap · Changelog · Support ·
Security · API stability
Companion projects
GraphCompose is MIT-licensed and solo-maintained. If it saves your team work,
GitHub Sponsors helps fund releases,
dependency updates, visual-regression coverage, and issue triage. Sponsorship does
not gate features or buy support priority; see Support.
License
MIT — see LICENSE.
评论