This page shows real-world recipes, ordered from simple to more advanced.
Goal: parse user input and update a live HTML preview.
Parser parser = Parser.builder()
.extensions(ExtensionDiscovery.load())
.build();
HtmlRenderer renderer = HtmlRenderer.builder()
.extensions(ExtensionDiscovery.load())
.softbreak("<br>")
.build();
String markdown = editor.getValue();
String previewHtml = renderer.render(parser.parse(markdown));
previewPane.setInnerHtml(previewHtml);
Why this works:
If you want raw HTML to stay as HTML:
HtmlRenderer renderer = HtmlRenderer.builder()
.escapeHtml(false)
.build();
If you want raw HTML to be shown literally:
HtmlRenderer renderer = HtmlRenderer.builder()
.escapeHtml(true)
.build();
Goal: render to a detached DOM fragment and mount it in the page.
Parser parser = Parser.builder()
.extensions(ExtensionDiscovery.load())
.build();
String markdown = """
Hello **world**
""";
Elemental2Renderer renderer = Elemental2Renderer.builder()
.extensions(ExtensionDiscovery.load())
.softBreakRendering(SoftBreakRendering.BR_ELEMENT)
.build();
DocumentFragment fragment = renderer.render(parser.parse(markdown));
previewContainer.appendChild(fragment);
Why this is useful:
Elemental2Renderer renderer = Elemental2Renderer.builder()
.rawHtmlHandler(new RawHtmlHandler() {
@Override
public elemental2.dom.Node renderInline(String literal) {
return null;
}
@Override
public elemental2.dom.Node renderBlock(String literal) {
return null;
}
})
.build();
Return null to fall back to safe default behavior, or return a custom DOM node if you want to materialize the raw HTML yourself.
Goal: extract text for previews, feeds, indexing, or notifications.
Parser parser = Parser.builder()
.extensions(ExtensionDiscovery.load())
.build();
String markdown = """
First line
Second line
""";
TextContentRenderer renderer = TextContentRenderer.builder()
.lineBreakRendering(LineBreakRendering.COMPACT)
.build();
String summary = renderer.render(parser.parse(markdown));
TextContentRenderer renderer = TextContentRenderer.builder()
.stripNewlines(true)
.build();
This is useful when you want a single-line summary.
TextContentRenderer renderer = TextContentRenderer.builder()
.lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS)
.build();
This keeps block boundaries more readable in exported text.
Goal: normalize user-authored Markdown into a stable format before persisting it.
Parser parser = Parser.builder()
.extensions(ExtensionDiscovery.load())
.build();
String userMarkdown = """
# Document
Body text.
""";
MarkdownRenderer renderer = MarkdownRenderer.builder()
.extensions(ExtensionDiscovery.load())
.build();
String canonicalMarkdown = renderer.render(parser.parse(userMarkdown));
database.save(canonicalMarkdown);
Use this when you want:
Canonical Markdown is not source-preserving.
That means the output may:
Goal: accept a narrow feature set for a controlled input surface.
Parser parser = Parser.builder()
.enabledBlockTypes(Set.of(Heading.class, Paragraph.class, ListBlock.class))
.build();
This is useful for:
Goal: use source spans to map parsed nodes back to the original text.
Parser parser = Parser.builder()
.includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
.build();
String markdown = """
Hello **world**
""";
Node document = parser.parse(markdown);
You can then inspect getSourceSpans() on nodes to associate parsed structure with editor ranges.
Goal: turn on the built-in extension set for a typical GitHub-flavored Markdown experience.
List<Extension> extensions = ExtensionDiscovery.load();
Parser parser = Parser.builder().extensions(extensions).build();
HtmlRenderer renderer = HtmlRenderer.builder().extensions(extensions).build();
This gives you:
Extension autolink = AutolinkExtension.builder()
.linkTypes(AutolinkType.URL)
.build();
Use this when you want to recognize bare URLs but not email addresses or www. prefixes.
Extension strikethrough = StrikethroughExtension.builder()
.requireTwoTildes(true)
.build();
This is a good fit when you want stricter Markdown parsing rules.
List<Extension> extensions = new ArrayList<>(ExtensionDiscovery.load());
extensions.add(new MyCustomExtension());
Parser parser = Parser.builder().extensions(extensions).build();
HtmlRenderer renderer = HtmlRenderer.builder().extensions(extensions).build();
The important point is that extensions are just values. You can assemble them however you want before passing them into the builders.
Goal: build HTML, text, and Markdown from one parsed document.
Parser parser = Parser.builder().extensions(ExtensionDiscovery.load()).build();
String markdown = """
# Title
Hello **world**
""";
Node document = parser.parse(markdown);
String html = HtmlRenderer.builder().extensions(ExtensionDiscovery.load()).build().render(document);
String text = TextContentRenderer.builder().extensions(ExtensionDiscovery.load()).build().render(document);
String canonical = MarkdownRenderer.builder().extensions(ExtensionDiscovery.load()).build().render(document);
This is useful for:
HtmlRenderer renderer = HtmlRenderer.builder()
.attributeProviderFactory(context -> new AttributeProvider() {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
if ("a".equals(tagName)) {
attributes.put("rel", "noopener");
}
}
})
.build();
Use this for:
Elemental2Renderer renderer = Elemental2Renderer.builder()
.attributeProviderFactory(context -> new ElementAttributeProvider() {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
if ("img".equals(tagName)) {
attributes.put("loading", "lazy");
}
}
})
.build();
Typical parser extension workflow:
public final class MyExtension implements Parser.ParserExtension {
@Override
public void extend(Parser.Builder parserBuilder) {
parserBuilder.customDelimiterProcessor(new MyDelimiterProcessor());
parserBuilder.postProcessor(new MyPostProcessor());
}
}
Then register it:
Parser parser = Parser.builder()
.extensions(List.of(new MyExtension()))
.build();
If your syntax needs custom rendering, register a renderer hook too:
public final class MyHtmlExtension implements HtmlRenderer.HtmlRendererExtension {
@Override
public void extend(HtmlRenderer.Builder rendererBuilder) {
rendererBuilder.nodeRendererFactory(context -> new MyHtmlNodeRenderer(context));
}
}
Goal: render Markdown files into static HTML.
Parser parser = Parser.builder().extensions(ExtensionDiscovery.load()).build();
HtmlRenderer renderer = HtmlRenderer.builder().extensions(ExtensionDiscovery.load()).build();
for (Path path : markdownFiles) {
String markdown = Files.readString(path);
String html = renderer.render(parser.parse(markdown));
Path output = outputDir.resolve(replaceExtension(path.getFileName().toString(), ".html"));
Files.writeString(output, html);
}
Goal: verify that a Markdown fragment round-trips predictably.
Parser parser = Parser.builder().extensions(ExtensionDiscovery.load()).build();
MarkdownRenderer markdownRenderer = MarkdownRenderer.builder().extensions(ExtensionDiscovery.load()).build();
HtmlRenderer htmlRenderer = HtmlRenderer.builder().extensions(ExtensionDiscovery.load()).build();
Node document = parser.parse(input);
String normalized = markdownRenderer.render(document);
String html = htmlRenderer.render(parser.parse(normalized));
This pattern is useful when you want to test semantic stability rather than exact source output.
Use these quick rules:
Parser when you need an ASTHtmlRenderer when you need HTMLElemental2Renderer when you need browser DOMTextContentRenderer when you need readable textMarkdownRenderer when you need canonical MarkdownGoal: attach predictable CSS hooks to the HTML or DOM that comes out of Markdown rendering.
Start with the built-in preset if you want the repository's default class naming scheme:
List<Extension> extensions = List.of(new DuiClassExtension());
Parser parser = Parser.builder()
.extensions(extensions)
.build();
HtmlRenderer htmlRenderer = HtmlRenderer.builder()
.extensions(extensions)
.build();
String html = htmlRenderer.render(parser.parse("# Title"));
The rendered heading will carry the generic dui class plus dui-md-heading and dui-md-h1. That makes it easy to theme all Markdown output consistently without replacing the renderer.
If you want your own naming scheme, use the generic extension directly:
MarkdownClassExtension markdownClasses =
MarkdownClassExtension.builder()
.classes("article-content", "prose")
.nodeClasses(Paragraph.class, "article-paragraph")
.nodeClasses(Heading.class, "article-heading")
.tagClasses("h1", "heading-xl")
.tagClasses("p", "paragraph-body")
.build();
List<Extension> extensions = List.of(markdownClasses);
Parser parser = Parser.builder()
.extensions(extensions)
.build();
Elemental2Renderer renderer = Elemental2Renderer.builder()
.extensions(extensions)
.build();
This is useful when you want:
The class extension merges with any classes already present on the element.
That means built-in renderer classes such as language-java on fenced code blocks stay intact while your custom class names are appended.
Start with the default builders and the built-in extension set.
Only customize when you can clearly explain the user-visible change you need.