ZetCode

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).

Main.java
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.

Main.java
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.

Main.java
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.

Main.java
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.

Main.java
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.

Main.java
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 ![image](photo.png).
        """;

    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.

Main.java
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.

src/main/resources/sample.md
# 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.

Main.java
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.

Main.java
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

commonmark-java documentation

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.