GraphCompose
Build production-ready PDF, Word and PowerPoint documents in Java — without manual layout calculations.
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.5.0 — PDF, PowerPoint and Word as three full outputs: the DOCX export writes the page the engine laid out — line heights, column widths, real Word lists, anchored drawings, page zones, headers and footers, and the faces the document is set in — with charts as Word charts and a report naming every node it could not carry, held to a 104-document corpus in Word and LibreOffice; plus the release train on Maven Central as one deployment, and a line copied out of a PDF keeps its emoji. See CHANGELOG.md.
· 🟡 In development: v2.5.1 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
Three full outputs from one document. The PPTX Java API, and the DOCX API added in
2.5, are Experimental (@Beta).
- PDF — fixed-layout output, included in
graph-compose.
- PPTX — editable fixed-layout slides, provided by the separate
graph-compose-render-pptx module.
- DOCX — an editable Word document written from the laid-out page, which Word
paginates, 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.
The same session exports to Word through a configured backend, with
graph-compose-render-docx on the classpath. Its report names what Word could not
carry as authored, and deterministic(true) gives the same bytes on every run on
one platform:
try (DocumentSession document = GraphCompose.document().create()) {
document.pageFlow().addParagraph("Quarterly report").build();
document.export(DocxSemanticBackend.builder()
.deterministic(true)
.reportSink(report -> report.notes().forEach(System.out::println))
.build(), Path.of("report.docx"));
}
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.5.0</version>
</dependency>
Gradle:
dependencies { implementation("io.github.demchaav:graph-compose:2.5.0") }
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.5.0</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, PPTX and DOCX are full outputs. graph-compose includes
the graph-compose-render-pdf backend; the PPTX and DOCX
backends are separate modules. The PPTX Java API, and the DOCX API added in 2.5, are
Experimental (@Beta).
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.
Each one brings the PDF module it measures text with, so a Word or PowerPoint export
needs only its own module beside graph-compose-core, graph-compose or the bundle.
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 / DOCX backend"]
C -. "DOCX also walks the tree" .-> E
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.
评论