Declarative Java DSL for structured business PDFs.
Describe what the document says; the engine resolves layout, pagination, themes, and PDFBox rendering. Cinematic by default.
Release status — 🟢 Latest stable: v2.0.0 — the module-first release: the engine is now a lean
graph-compose-corewith opt-inrender-pdf/render-docxbackends, whilegraph-composestays a drop-in for PDF. Migrating to 2.0 ↓
· ⬆️ Upgrading from 1.x?
graph-composestays a drop-in for PDF with no code change; see the 2.0 modules migration guide · See API stability policy for tier definitions.
Live Showcase · Examples Gallery · Docs · Changelog
☝ This banner is itself a GraphCompose document — view the full module-first deck (PDF), rendered by EngineDeckV2Example: the 2.0 module graph, native vector charts, and real comparative benchmarks, all drawn by the engine. It renders its own marketing.
- Author intent, not coordinates. Fluent DSL for sections, paragraphs, tables, lists, layer stacks, themes — the engine handles measurement, pagination, and rendering.
- Deterministic by design. Two-pass layout. Snapshots are stable across machines, so layout regressions are catchable in tests before any byte ships.
- Cinematic by default. Soft panels, accent strips, transforms, native vector charts, and gradients are first-class primitives, not workarounds.
- Lean core, pluggable backends. The
graph-compose-coreengine carries no PDFBox or POI; render backends are separate modules discovered viaServiceLoader— PDF is one dependency away (or already included ingraph-compose), DOCX/PPTX are opt-in — see support matrix.
Sits between iText (low-level page primitives) and JasperReports (XML-template-driven layout): a Java DSL describes the document semantically, the engine renders.
The module-first release — the single jar becomes a family of per-concern artifacts, so you install exactly what you render.
- Lean engine —
graph-compose-coreis the document model, DSL, themes, and deterministic layout with no PDFBox, POI, or template code on its dependency tree. Backends plug in through aServiceLoaderseam; a core-only classpath asked to render throwsMissingBackendExceptionnaming the artifact to add. - Opt-in render backends —
graph-compose-render-pdf(PDFBox 3.0, full DSL coverage),graph-compose-render-docxandgraph-compose-render-pptx(Apache POI, semantic export). graph-composestays a drop-in — the 1.x coordinate is now a thin wrapper over core + the PDF backend, so existing callers upgrade with no code and no dependency change.- Templates are their own artifact — the CV / cover-letter / invoice / proposal preset families moved to
graph-compose-templates(imports unchanged). This is the one dependency-level break of the split. graph-compose-bundle— one batteries-included coordinate: PDF stack + templates + fonts + colour emoji.- Retired surface — the APIs deprecated across 1.6–1.9 are removed, the layered template packages dropped their
.v2suffix, andBusinessThemeplus the classic pre-layered presets are gone — each removal has a named replacement in the migration guide.
Everything the 1.9 line added — in-document navigation, native TOC and page references, bookmarks, multi-section documents, inline chips / SVG icons / colour emoji, render-to-image — ships unchanged in 2.0. Full history in CHANGELOG.md.
Requires Java 17+ (enforced by the build).
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>2.0.0</version>
</dependency>dependencies { implementation("io.github.demchaav:graph-compose:2.0.0") }Which artifact? (2.0 module split).
graph-composeabove is the drop-in default — it renders PDF out of the box because it aggregates the leangraph-compose-coreengine plus thegraph-compose-render-pdfbackend, so existing 1.x callers upgrade with no code change. Reach for a different coordinate only to take less or more:
Goal Depend on PDF — the 1.x default graph-composeBatteries-included (PDF + templates + fonts + emoji) graph-compose-bundleLean core, bring your own backend graph-compose-coreBuilt-in CV / cover-letter / invoice / proposal templates add graph-compose-templatesDOCX / PPTX export add graph-compose-render-docx/graph-compose-render-pptxEvery 2.0 coordinate shares the
graph-composeversion (the fonts and emoji companions keep their own lines). A baregraph-compose-corerenders nothing until a backend is on the classpath — asking it to build a PDF throwsMissingBackendException, which names the artifact to add (graph-compose-render-pdf, already included ingraph-compose).
Companion artifacts: fonts & colour emoji. Two opt-in companions carry their own version lines (they change on their own cadence, so an engine upgrade never re-downloads them):
graph-compose-fonts:1.0.0— the curated Google font families (~18 MB; pure-text and standard-14 documents need nothing extra; details in the fonts migration note) — andgraph-compose-emoji:1.0.0— inline colour emoji forRichText.emoji(":star:", size)(an unknown shortcode falls back to its literal text, so documents without emoji render unchanged). Both are already included ingraph-compose-bundle.
Distribution — Maven Central is the canonical channel from v1.6.6 onwards (
io.github.demchaav:graph-compose:<version>). Hosted Javadocs auto-publish to javadoc.io/doc/io.github.demchaav/graph-compose shortly after each Central release. The legacy JitPack URL (com.github.DemchaAV:GraphCompose:v<version>) remains resolvable for callers pinned to v1.6.5 and earlier but is no longer the documented install option.
Upgrading from 1.x? Rendering PDF through
graph-composeneeds no change at all. If you reached the built-in templates through the single 1.x jar, addgraph-compose-templates(imports are unchanged) — the 2.0 migration guide walks every case, including the removed deprecated APIs and their replacements.
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentTextDecoration;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.font.FontName;
import java.nio.file.Path;
class Hello {
public static void main(String[] args) throws Exception {
// A small inline palette — swap these for your own brand colours.
DocumentColor cream = DocumentColor.rgb(252, 248, 240);
DocumentColor panel = DocumentColor.rgb(244, 238, 228);
DocumentColor accent = DocumentColor.rgb(196, 153, 76);
DocumentTextStyle h1 = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD).size(28)
.decoration(DocumentTextDecoration.BOLD)
.color(DocumentColor.rgb(20, 60, 75)).build();
DocumentTextStyle body = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA).size(11)
.color(DocumentColor.rgb(34, 38, 50)).build();
try (DocumentSession document = GraphCompose.document(Path.of("hello.pdf"))
.pageSize(DocumentPageSize.A4)
.pageBackground(cream)
.margin(28, 28, 28, 28)
.create()) {
document.pageFlow(page -> page
.addSection("Hero", section -> section
.softPanel(panel, 10, 14)
.accentLeft(accent, 4)
.addParagraph(p -> p.text("GraphCompose").textStyle(h1))
.addParagraph(p -> p.text("A cinematic hero, no manual coordinates.")
.textStyle(body))));
document.buildPdf();
}
}
}For a Spring Boot @RestController streaming the PDF straight to the response, see HttpStreamingExample.
| Format | Status | Notes |
|---|---|---|
| Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. | |
| DOCX | Partial | Semantic export via Apache POI. Unsupported nodes (shape, line, ellipse, barcode) are dropped silently — layout fidelity is best-effort for paragraph / list / table content. |
| PPTX | Skeleton | Validates supported node types and emits a manifest. Not a real PowerPoint export yet — planned only if there is demand. |
- Text is laid out left-to-right. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are not performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in #140.
- A glyph the active font does not cover renders as
?(with a warning logged); load a font that covers the script you need.
- Server-side PDF generation in Java — invoices, CVs, reports, proposals, statements, schedules.
- Templated documents from data — themed presets (
ModernProfessional,ModernInvoice, …) you parameterise instead of re-styling every time. - Regression-tested layouts —
DocumentSession#layoutSnapshot()makes layout changes visible in PRs before any byte ships;PdfVisualRegressionadds a pixel-level gate for font and colour fidelity. - Streaming PDFs from web backends — Spring Boot
@RestControllerwriting straight to the response (HttpStreamingExample). - Higher-level than PDFBox, lighter than JasperReports — Java DSL describes semantics; no XML templates, no manual coordinates.
- Not a hosted PDF rendering service — it is a library you embed.
- Not a WYSIWYG editor — the DSL is code, not drag-and-drop.
- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled
.jasperfiles. - Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported.
| Library | API style | Layout | License | Best for |
|---|---|---|---|---|
| GraphCompose | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests |
| PDFBox | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction |
| iText 7 | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence |
| OpenPDF | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases |
| JasperReports | XML templates compiled to .jasper |
Template-driven | LGPL | Tabular reports with datasource bindings |
GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer.
| You want to… | Surface | Entry point |
|---|---|---|
| Generate a one-off PDF programmatically | DSL | GraphCompose.document(...).pageFlow(...) — see Hello world above |
| Generate a CV / cover letter from data | Layered templates | ModernProfessional.create().compose(session, cvDocument) — see layered templates |
| Add a custom visual primitive | Engine extension | NodeDefinition + PdfFragmentRenderHandler — see extension guide |
| Regression-test generated layouts | Layout snapshots | DocumentSession#layoutSnapshot() — quickstart at Testing your document; full reference at snapshot testing |
| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | PdfVisualRegression.standard()…assertMatchesBaseline(...) — see visual regression testing |
| See the live gallery | Static showcase site | Showcase — source under web/, deployed to GitHub Pages via the Pages workflow |
Templates in 2.0 — there is one template surface: the layered preset families in
graph-compose-templates, themed throughBrandTheme. Arriving from a pre-2.0 surface (classic presets, the built-in*Templateclasses)? Which template system should I use? maps every retired name to its layered replacement.
Three snippets from the vector surfaces. Full runnable versions live in the examples gallery.
Native chart — categories + series in, native vector bars out (no rasterization).
ChartData revenue = ChartData.builder()
.categories("Q1", "Q2", "Q3", "Q4")
.series("2024", 12.4, 15.1, 9.8, 14.2)
.series("2025", 14.0, 18.2, 11.3, 16.9)
.build();
section.chart(ChartSpec.bar().data(revenue)
.legend(LegendPosition.BOTTOM)
.size(ChartSize.aspectRatio(16, 7))
.build());Overshoot-free line — a smooth curve constrained to never overshoot the data range.
section.chart(ChartSpec.line().data(series)
.interpolation(LineInterpolation.MONOTONE)
.build());SVG import + alignment — parse SVG to native geometry, seat any fixed node across the width.
SvgIcon globe = SvgIcon.parse(svgMarkup);
flow.addSvgIcon(globe, 48, HorizontalAlign.CENTER);
flow.addAligned(HorizontalAlign.RIGHT, anyFixedNode);GraphCompose splits into a public canonical surface you author against (com.demcha.compose.document.*) and an internal shared engine foundation (com.demcha.compose.engine.*, marked @Internal) that resolves geometry, pagination, and rendering behind it. Since 2.0 that boundary is also a packaging boundary: the surface and engine ship in graph-compose-core, and each render backend is a separate module that registers through a ServiceLoader seam. You author intent; the engine resolves the rest.
flowchart LR
A["GraphCompose.document(...)<br/>DocumentSession · DocumentDsl"] --> B["DocumentNode tree<br/>document.node"]
B --> C["LayoutCompiler<br/>document.layout"]
C --> D["Engine foundation @Internal<br/>measure → paginate → place"]
D --> E{ServiceLoader}
E -->|render-pdf| F["PdfFixedLayoutBackend<br/>PDFBox"]
E -->|render-docx| G["DocxSemanticBackend<br/>POI"]
D -.->|layoutSnapshot| H["Deterministic snapshot<br/>(regression tests)"]
Full detail: architecture overview · package map · lifecycle.
The repository is a Maven multi-module reactor: the root pom.xml is the build aggregator, so ./mvnw clean verify at the root builds and tests every module (scope to one with -pl :<artifactId>; the lean engine lives in core/).
- Published to Maven Central
graph-compose-core(core/) — the lean document enginegraph-compose-render-pdf·-render-docx·-render-pptx— render backendsgraph-compose-templates— built-in CV / cover-letter / invoice / proposal presetsgraph-compose-testing— snapshot & visual-regression test helpersgraph-compose— the drop-in wrapper (core + PDF);graph-compose-bundle— batteries-included (adds templates + fonts + emoji)
- Companion artifacts (independent version lines) —
graph-compose-fonts,graph-compose-emoji - Development only (never published) —
qa(architecture guards + visual regression),coverage(aggregate JaCoCo),examples,benchmarks
See CONTRIBUTING for the branch-routing table and the full build / verify flow.
📚 Full docs index — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
- Templates — layered architecture — the template surface: CV, cover-letter, invoice, and proposal preset families on
BrandTheme. Personas: quickstart · using templates · authoring presets · contributing a new family. - Which template system? — the template naming history and the migration map for callers arriving from a pre-2.0 surface (classic presets, built-in
*Templateclasses, the legacy PDF API). The retired classic docs are archived at v1-classic.
- Architecture overview · Lifecycle · Production rendering · Benchmarks · Layout snapshot testing · Troubleshooting
- Recipes index — shape-as-container · shapes · transforms · page-backgrounds · layered-page-design · absolute-placement · tables · themes · streaming · extending · font-coverage
- Examples gallery — every runnable example with PDF preview
- Contributing · Code of conduct · Security policy · Release process
- API stability policy · Which template system? · Migration to 2.0 (modules) · older migration notes
- graph-compose-markdown — a Markdown → PDF path built on the GraphCompose engine. Hand it a Markdown document and it renders through the same layout, theme, and PDFBox pipeline as the Java DSL — a companion input surface for teams who would rather author in Markdown than call the DSL directly. Published on Maven Central as
io.github.demchaav:graph-compose-markdown; independent lifecycle, consumes the engine as a dependency. - graphcompose-ai-flow — experimental sister project exploring an AI-assisted authoring flow on top of GraphCompose. Independent codebase, separate lifecycle — nothing in this repo depends on it. Track it if you are interested in agentic document composition driven by the same semantic node model.
MIT — see LICENSE.

