Java CommonMark
last modified July 4, 2026
In this article we show how to work with the commonmark-java library which is used to parse and render Markdown in Java. We provide several code examples to work with Markdown in Java.
Markdown is a lightweight markup language with plain text formatting syntax. It is widely used for documentation, readme files, blog posts, and note-taking. The commonmark-java library implements the CommonMark specification, a strongly defined, highly compatible standard that resolves ambiguities found in the original Markdown specification.
commonmark-java also supports GitHub-Flavored Markdown (GFM) extensions including tables, task lists, strikethrough, and autolinks, making it suitable for rendering the same Markdown that developers use on platforms like GitHub.
CommonMark library
commonmark-java is a Java library for parsing and rendering Markdown according to the CommonMark specification. It started as a port of commonmark.js and has evolved into a fast, extensible library. It parses input into an abstract syntax tree (AST), allows visiting and manipulating nodes, and renders to HTML or back to Markdown. The core library has zero dependencies and extensions are packaged in separate artifacts.
<dependencies>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-task-list-items</artifactId>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-strikethrough</artifactId>
<version>0.22.0</version>
</dependency>
</dependencies>
These are the Maven dependencies for commonmark-java, including the core library
and extensions for tables, task lists, and strikethrough. All artifacts share
the same version and live under the org.commonmark group ID.
Parsing Markdown
The first example shows how to parse a Markdown string into an abstract syntax tree (AST).
import org.commonmark.parser.Parser;
import org.commonmark.node.*;
void main() {
var markdown = """
# Hello, CommonMark!
This is a **Markdown** document with a [link](https://commonmark.org).
""";
var parser = Parser.builder().build();
var document = parser.parse(markdown);
System.out.println(document);
}
The example parses a Markdown string using the default parser and prints the resulting AST.
var markdown = """
# Hello, CommonMark!
This is a **Markdown** document with a [link](https://commonmark.org).
""";
A Markdown string is defined using a Java text block. The string contains a heading, a paragraph with bold text, and a link.
var parser = Parser.builder().build(); var document = parser.parse(markdown);
The Parser is built using the builder pattern. The
parse method converts the Markdown string into a
Document AST node.
$ java -cp "lib/*" Main.java
Document{}
The output is Document{}. The
Document class does not override toString, so it
falls back to the default Object.toString implementation, which
prints the class name and an empty braces pair. The parsed content is not lost
— the Document object contains a full tree of AST nodes
representing the heading, paragraph, bold text, and link.
To actually see the result, you need to pass the document to an
HtmlRenderer or traverse the node tree manually. We cover rendering
to HTML in the next section.
Rendering to HTML
The most common use case is converting Markdown to HTML.
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
void main() {
var markdown = """
# Features
CommonMark supports **bold**, *italic*, and `inline code`.
- Item one
- Item two
- Item three
""";
var parser = Parser.builder().build();
var renderer = HtmlRenderer.builder().build();
var document = parser.parse(markdown);
var html = renderer.render(document);
System.out.println(html);
}
The example converts a Markdown string into an HTML string.
var renderer = HtmlRenderer.builder().build();
An HtmlRenderer is built using its builder. It transforms the AST
into an HTML string.
var html = renderer.render(document);
The render method takes the parsed document and produces an HTML
string. The result includes standard HTML tags such as
<h1>, <p>, <strong>,
<em>, <ul>, and <li>.
The HtmlRenderer builder provides several safety options:
escapeHtml(true) escapes raw HTML tags and blocks in the input,
and sanitizeUrls(true) strips potentially unsafe URLs from
<a> and <img> tags.
Rendering back to Markdown
commonmark-java can also render an AST back to Markdown. This is useful for normalizing or programmatically generating Markdown content.
import org.commonmark.node.*;
import org.commonmark.renderer.markdown.MarkdownRenderer;
void main() {
var heading = new Heading();
heading.setLevel(2);
heading.appendChild(new Text("Generated Heading"));
var document = new Document();
document.appendChild(heading);
var renderer = MarkdownRenderer.builder().build();
var markdown = renderer.render(document);
System.out.println(markdown);
}
The example builds a document programmatically and renders it back to Markdown.
var heading = new Heading();
heading.setLevel(2);
heading.appendChild(new Text("Generated Heading"));
A Heading node is created with level 2 and a Text
child node containing the heading text.
var renderer = MarkdownRenderer.builder().build(); var markdown = renderer.render(document);
The MarkdownRenderer takes an AST and produces a Markdown string.
$ java -cp "lib/*" Main.java ## Generated Heading
Using extensions
commonmark-java provides numerous extensions for GitHub-Flavored Markdown
features such as tables, task lists, and strikethrough. Extensions are registered
directly on the Parser and HtmlRenderer builders.
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.ext.task.list.TaskListItemsExtension;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import java.util.List;
void main() {
var extensions = List.of(
TablesExtension.create(),
TaskListItemsExtension.create(),
StrikethroughExtension.create());
var parser = Parser.builder()
.extensions(extensions)
.build();
var renderer = HtmlRenderer.builder()
.extensions(extensions)
.build();
var markdown = """
## Extensions Demo
| Name | Price |
|-------|-------|
| Book | $12 |
| Pen | $2 |
- [x] Learn CommonMark
- [ ] Write article
- [ ] Publish
This is ~~deprecated~~ updated.
""";
var html = renderer.render(parser.parse(markdown));
System.out.println(html);
}
The example enables table, task list, and strikethrough extensions.
var extensions = List.of(
TablesExtension.create(),
TaskListItemsExtension.create(),
StrikethroughExtension.create());
Extensions are created with their static factory methods and collected in a list.
var parser = Parser.builder()
.extensions(extensions)
.build();
var renderer = HtmlRenderer.builder()
.extensions(extensions)
.build();
The same list of extensions is passed to both the parser and the renderer via
the extensions method. This ensures that both components are aware
of the additional syntax.
| Name | Price | |-------|-------| | Book | $12 | | Pen | $2 |
Tables are defined using pipe characters and hyphens. The first row is the header, and the second row defines the column alignment.
- [x] Learn CommonMark - [ ] Write article
Task lists use the [ ] and [x] syntax for unchecked
and checked items respectively.
This is ~~deprecated~~ updated.
Strikethrough text is marked with double tildes ~~.
Visiting the AST
After parsing, the result is a tree of nodes. You can traverse the AST using the visitor pattern to inspect or modify the document before rendering.
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
void main() {
var markdown = """
# Title
Some **bold** and *italic* text in this paragraph.
""";
var parser = Parser.builder().build();
var document = parser.parse(markdown);
var visitor = new WordCounter();
document.accept(visitor);
System.out.println("Word count: " + visitor.wordCount);
System.out.println("Heading count: " + visitor.headingCount);
}
class WordCounter extends AbstractVisitor {
int wordCount = 0;
int headingCount = 0;
@Override
public void visit(Text text) {
wordCount += text.getLiteral().split("\\W+").length;
visitChildren(text);
}
@Override
public void visit(Heading heading) {
headingCount++;
visitChildren(heading);
}
}
The example defines a visitor that counts words and headings in the document.
class WordCounter extends AbstractVisitor {
The visitor extends AbstractVisitor, which provides default
implementations for all node types.
@Override
public void visit(Text text) {
wordCount += text.getLiteral().split("\\W+").length;
visitChildren(text);
}
The visit(Text) method is called for every text node. It splits
the literal text on non-word characters and counts the resulting tokens.
The call to visitChildren ensures that child nodes are also visited.
Customizing HTML attributes
The AttributeProvider interface allows you to add or modify HTML
attributes on rendered elements without changing the rendering logic itself.
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;
import java.util.Map;
void main() {
var parser = Parser.builder().build();
var renderer = HtmlRenderer.builder()
.attributeProviderFactory(context -> new ClassAttributeProvider())
.build();
var markdown = """
# Welcome
This is a [link](https://example.com) and an .
""";
var document = parser.parse(markdown);
var html = renderer.render(document);
System.out.println(html);
}
class ClassAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName,
Map<String, String> attributes) {
if (node instanceof Image) {
attributes.put("class", "img-fluid rounded");
}
if (node instanceof Link) {
attributes.put("target", "_blank");
attributes.put("rel", "noopener");
}
}
}
The example adds CSS classes to images and security attributes to external links.
var renderer = HtmlRenderer.builder()
.attributeProviderFactory(context -> new ClassAttributeProvider())
.build();
An AttributeProviderFactory is registered on the renderer. It
creates a new AttributeProvider instance for each rendering.
if (node instanceof Image) {
attributes.put("class", "img-fluid rounded");
}
When the renderer encounters an Image node, the provider adds a
Bootstrap-compatible class attribute.
Source positions
commonmark-java can track the positions of nodes in the original source text, which is useful for creating editor integrations or linters.
import org.commonmark.parser.IncludeSourceSpans;
import org.commonmark.parser.Parser;
void main() {
var parser = Parser.builder()
.includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
.build();
var source = """
# Title
A paragraph with *emphasis* here.
""";
var doc = parser.parse(source);
var emphasis = doc.getLastChild().getLastChild();
var span = emphasis.getSourceSpans().get(0);
System.out.printf("Line: %d%n", span.getLineIndex());
System.out.printf("Column: %d%n", span.getColumnIndex());
System.out.printf("Input index: %d%n", span.getInputIndex());
System.out.printf("Length: %d%n", span.getLength());
var matched = source.substring(
span.getInputIndex(),
span.getInputIndex() + span.getLength());
System.out.println("Matched text: " + matched);
}
The example enables source spans and retrieves the position of the emphasis node in the original text.
var parser = Parser.builder()
.includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
.build();
The BLOCKS_AND_INLINES option enables tracking for both block-level
and inline nodes. Use BLOCKS if you only need block positions.
var span = emphasis.getSourceSpans().get(0);
Each node has a list of source spans. A span records the line index, column index, input index, and length of the original source that produced the node.
Reading Markdown from a file
In practice, Markdown content is often stored in files. The next example reads a Markdown file and renders it to HTML.
# Sample Document
## Introduction
This is a sample Markdown file with **formatted** text.
### Code Example
```java
record Point(int x, int y) {
Point translate(int dx, int dy) {
return new Point(x + dx, y + dy);
}
}
```
We have a sample Markdown file with a heading, formatted text, and a fenced code block.
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
void main() throws IOException {
var filePath = Path.of("src/main/resources/sample.md");
var markdown = Files.readString(filePath);
var parser = Parser.builder().build();
var renderer = HtmlRenderer.builder().build();
var html = renderer.render(parser.parse(markdown));
var outputPath = Path.of("sample.html");
Files.writeString(outputPath, html);
System.out.println("HTML written to " + outputPath.toAbsolutePath());
}
The example reads Markdown from a file, renders it to HTML, and writes the output to another file.
var filePath = Path.of("src/main/resources/sample.md");
var markdown = Files.readString(filePath);
The Files.readString method reads the entire Markdown file into a
string. It uses UTF-8 encoding by default.
Alternatively, you can parse directly from a Reader using
parser.parseReader(reader), which avoids loading the entire file
into memory at once.
Wrapping HTML with CSS
When embedding rendered Markdown in a Swing application or web page, it is useful to wrap the HTML body with a full document structure including CSS styling.
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
void main() {
var parser = Parser.builder().build();
var renderer = HtmlRenderer.builder().build();
var markdown = """
## Styled Output
This Markdown is rendered with custom CSS styling.
""";
var body = renderer.render(parser.parse(markdown));
var css = """
body {
font-family: "Segoe UI", Roboto, sans-serif;
font-size: 14px;
line-height: 1.6;
padding: 20px;
color: #333;
text-align: left;
}
h2 { color: #2c3e50; }
""";
var fullHtml = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>%s</style>
</head>
<body>%s</body>
</html>
""".formatted(css, body);
System.out.println(fullHtml);
}
The example renders Markdown to HTML and wraps the result in a complete HTML
document with embedded CSS styles. This approach is especially useful when
displaying Markdown in JEditorPane or similar Swing components.
var body = renderer.render(parser.parse(markdown));
The Markdown is first parsed and rendered into an HTML body fragment.
var fullHtml = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>%s</style>
</head>
<body>%s</body>
</html>
""".formatted(css, body);
The HTML body and CSS are embedded into a full HTML document using a text block
template and the formatted method for string interpolation.
In this article we have worked with the commonmark-java library, which implements the CommonMark specification. We have parsed Markdown strings, rendered them to HTML, converted ASTs back to Markdown, enabled extensions for GitHub-Flavored Markdown, walked the AST with visitors, customized HTML attributes, tracked source positions, and wrapped HTML output with CSS styling.
Source
Author
List all Java tutorials.