Enterprise Java

Markdown Rendering with commonmark-java

Markdown has become the de facto standard for writing documentation, technical blogs, README files, knowledge bases, and collaborative content. While Markdown itself is easy to write, applications often need to convert Markdown into HTML before displaying it in browsers. The commonmark-java library provides a robust Java implementation of the CommonMark specification. It allows developers to parse Markdown into an Abstract Syntax Tree (AST), inspect and manipulate nodes, customize rendering, and generate HTML output with minimal effort.

1. Overview

CommonMark is a standardized specification for Markdown. Unlike earlier Markdown implementations that behaved differently across platforms, CommonMark ensures consistent parsing rules. The commonmark-java library is the official Java implementation that fully supports the CommonMark specification while providing extension points for customization.

1.1 Key Features of commonmark-java

  • Markdown Parsing: The library parses raw Markdown text and converts it into a structured Abstract Syntax Tree (AST). Instead of processing plain text directly, applications work with a hierarchy of nodes representing headings, paragraphs, lists, links, images, code blocks, tables, and other Markdown elements. This structured representation simplifies content analysis and transformation.
  • HTML Rendering: Once the Markdown document has been parsed into an AST, the HtmlRenderer traverses the tree and generates standards-compliant HTML. The renderer correctly handles nested elements, inline formatting, block elements, and escaping of special characters, ensuring safe and consistent HTML output.
  • AST Traversal: Every Markdown document is represented as an Abstract Syntax Tree (AST), where each Markdown construct is a separate node. Developers can traverse this tree using the Visitor pattern to inspect, analyze, validate, or transform specific parts of the document before rendering it.
  • Custom Node Visitors: The library provides visitor APIs that allow developers to perform custom operations while traversing the parsed document. For example, applications can extract headings for a table of contents, collect hyperlinks, validate Markdown syntax, count images, generate summaries, or build search indexes without modifying the original document.
  • Custom HTML Rendering: Developers can customize how individual Markdown elements are rendered into HTML. Using custom node renderers and attribute providers, applications can add CSS classes, inject custom attributes, modify generated tags, wrap elements inside additional HTML, or replace the default rendering behavior entirely to meet specific application requirements.
  • Extension Support: The core library can be extended through optional modules that introduce additional Markdown features. Extensions enable support for functionality such as tables, task lists, autolinks, strikethrough, footnotes, and other advanced Markdown constructs while maintaining compatibility with the CommonMark specification.
  • GitHub-Flavored Markdown (GFM) Extensions: Through extension modules, commonmark-java supports many GitHub-Flavored Markdown features commonly used in README files and documentation. These include tables, task lists, automatic URL detection, and strikethrough formatting, making it suitable for rendering GitHub-style content.
  • High Performance: The library is lightweight, efficient, and optimized for production use. Its parser and renderer are designed to process large Markdown documents with minimal memory overhead, making it well suited for documentation portals, blogging platforms, content management systems, and other high-throughput Java applications.

2. Understanding the commonmark-java Library

The library is organized into multiple components that work together.

ComponentDescriptionPurposeCommon Use Cases
ParserThe Parser is the entry point of the commonmark-java library. It reads raw Markdown text and converts it into a structured Abstract Syntax Tree (AST), where each Markdown element becomes a node in the document tree.Transform Markdown syntax into an object model that can be traversed, analyzed, modified, or rendered.Parsing README files, documentation, blog posts, user-generated content, knowledge-base articles, and Markdown documents.
NodeEvery Markdown construct is represented as a subclass of Node. Examples include Document, Heading, Paragraph, Text, Link, Image, BulletList, Code, and many others. Together, these nodes form the AST.Provide a structured representation of the Markdown document that applications can inspect and manipulate.Extracting headings, finding links, counting images, validating document structure, or transforming specific Markdown elements.
VisitorThe Visitor API provides a convenient mechanism for traversing the AST. Developers typically extend AbstractVisitor and override only the node types they want to process, while allowing the framework to continue traversing child nodes automatically.Traverse the document tree and perform custom operations on selected Markdown elements.Building a table of contents, collecting hyperlinks, generating search indexes, validating content, extracting metadata, or producing document statistics.
HtmlRendererThe HtmlRenderer walks through the parsed AST and generates standards-compliant HTML. It correctly renders headings, paragraphs, emphasis, lists, block quotes, code blocks, links, images, and other Markdown elements while preserving their hierarchy.Convert the parsed Markdown document into browser-friendly HTML.Rendering documentation websites, CMS pages, blogs, developer portals, knowledge bases, email templates, and web applications.
Extensionscommonmark-java supports optional extension modules that add functionality beyond the core CommonMark specification. These extensions integrate seamlessly with both the parser and renderer.Enable additional Markdown capabilities without changing applicationcode significantly.GitHub-Flavored Markdown (GFM), tables, task lists, strikethrough, automatic links, footnotes, custom syntaxes, and organization-specific Markdown extensions.

2.1 Common Use Cases

Use CaseDescriptionBenefit
Documentation PlatformsRender Markdown-based documentation into responsive HTML pages for developer portals, product documentation, API references, and user guides.Enables technical writers to author documentation in Markdown while automatically generating consistent and browser-friendly HTML.
Knowledge BasesStore articles and FAQs in Markdown format and render them dynamically when users access the content.Simplifies content management while keeping documents lightweight, portable, and easy to edit.
Blog EnginesConvert Markdown blog posts into HTML before publishing them on websites or content management systems.Allows authors to focus on writing content while the application automatically generates clean and standards-compliant HTML.
Search EnginesExtract plain text from Markdown documents using TextContentRenderer for indexing and searching.Improves search accuracy by indexing readable text instead of Markdown syntax or HTML markup.
Content ValidationTraverse the Abstract Syntax Tree (AST) using visitors to inspect headings, links, images, code blocks, and other Markdown elements.Helps validate document structure, enforce formatting rules, detect broken links, and generate document statistics.
Custom Content Management Systems (CMS)Customize the generated HTML by implementing custom node renderers and attribute providers based on application-specific requirements.Produces branded, SEO-friendly, and framework-compatible HTML while maintaining the simplicity of Markdown authoring.
Static Site GeneratorsParse Markdown files and generate static HTML pages during the build process.Enables fast website generation with minimal runtime overhead and improved page loading performance.
Developer Tools and IDE PluginsProvide live Markdown previews, syntax analysis, and document transformation features inside development environments.Enhances the authoring experience by offering real-time rendering, validation, and preview capabilities.
Email and Report GenerationAuthor reports or email templates in Markdown and convert them into HTML before sending or publishing.Reduces template complexity while generating rich, well-formatted HTML content automatically.

2.2 Advantages

  • Official CommonMark implementation for Java
  • Simple and intuitive API
  • AST-based parsing enables advanced processing
  • Custom HTML rendering support
  • Visitor pattern for node traversal
  • Extension support for additional Markdown features
  • High performance and production-ready
  • Suitable for documentation, blogs, CMS, and developer tools

2.3 Limitations

  • The core library converts Markdown to HTML, not HTML to Markdown.
  • GitHub-Flavored Markdown features require additional extension modules.
  • Complex rendering customizations require familiarity with the node renderer APIs.

3. Parsing and Rendering Markdown

3.1 Maven Dependency

Add the following Maven dependency to your project’s pom.xml file to include the commonmark-java library.

<dependency>
    <groupId>org.commonmark</groupId>
    <artifactId>commonmark</artifactId>
    <version>stable__jar__version</version>
</dependency>

3.2 Parsing Markdown and Rendering HTML

3.2.1 Sample Markdown Input

Consider the following sample Markdown document that contains headings, bold text, a bullet list, and a hyperlink. We will use this document throughout the examples to demonstrate parsing, traversing the AST, and rendering HTML using the commonmark-java library.

# Java Guide

Welcome to **CommonMark**.

## Features

- Parser
- Renderer
- Visitor

Visit https://example.com

3.2.2 Java Example

The following example demonstrates the complete workflow of using the commonmark-java library. It creates a parser, converts a Markdown document into an Abstract Syntax Tree (AST), renders the parsed document as HTML, traverses the AST using the Visitor pattern to inspect individual nodes, and finally extracts the plain text content from the Markdown document.

// CommonMarkExample.java
import org.commonmark.node. * ;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.text.TextContentRenderer;

public class CommonMarkExample {

  public static void main(String[] args) {
    String markdown = "# Java Guide\n\n" + "Welcome to **CommonMark**.\n\n" + "## Features\n\n" + "- Parser\n" + "- Renderer\n" + "- Visitor\n";

    // Create parser
    Parser parser = Parser.builder().build();
    // Parse markdown
    Node document = parser.parse(markdown);

    // Render HTML
    HtmlRenderer renderer = HtmlRenderer.builder().build();
    String html = renderer.render(document);
    System.out.println("Generated HTML");
    System.out.println(html);

    // Process parsed nodes
    document.accept(new AbstractVisitor() {
      @Override
      public void visit(Heading heading) {
        System.out.println("Heading Level : " + heading.getLevel());
        visitChildren(heading);
      }

      @Override
      public void visit(Text text) {
        System.out.println("Text : " + text.getLiteral());
      }

      @Override
      public void visit(BulletList bulletList) {
        System.out.println("Bullet List");
        visitChildren(bulletList);
      }

      @Override
      public void visit(ListItem item) {
        System.out.println("List Item");
        visitChildren(item);
      }
    });

    // Render plain text (Markdown -> Text)
    TextContentRenderer textRenderer = TextContentRenderer.builder().build();
    String plainText = textRenderer.render(document);
    System.out.println("\nExtracted Text");
    System.out.println(plainText);
  }
}
3.2.2.1 Code Explanation

The example begins by importing the required classes from the commonmark-java library for parsing Markdown, rendering HTML, traversing the Abstract Syntax Tree (AST), and extracting plain text. Inside the main() method, a sample Markdown document is created as a Java string containing headings, bold text, and a bullet list. A Parser instance is then created using the builder pattern, and the parse() method converts the Markdown into a Document node, which serves as the root of the AST. Next, an HtmlRenderer is initialized to traverse the AST and generate standards-compliant HTML, which is stored in a string and printed to the console. The example then demonstrates AST traversal by invoking the accept() method on the document and passing an anonymous implementation of AbstractVisitor. The visitor overrides the visit() methods for Heading, Text, BulletList, and ListItem nodes to print information about each Markdown element encountered during traversal, while the visitChildren() method ensures that child nodes are recursively visited. Finally, the program creates a TextContentRenderer instance to render the same parsed document as plain text by removing all Markdown formatting, demonstrating how a single parsed AST can be reused for multiple purposes such as HTML generation, document analysis, metadata extraction, content validation, search indexing, and plain-text rendering without requiring the Markdown document to be parsed again.

3.2.2.2 Code Output

The output demonstrates the three major capabilities of the commonmark-java library. The first section, Generated HTML, shows that the Markdown document has been successfully converted into standards-compliant HTML, where headings become <h1> and <h2> elements, bold text is rendered using the <strong> tag, and the Markdown bullet list is transformed into an unordered HTML list (<ul> and <li>). The second section displays the output produced by the custom AbstractVisitor, which traverses the Abstract Syntax Tree (AST) and prints information about each node encountered, including heading levels, text nodes, bullet lists, and individual list items, demonstrating how developers can inspect and process Markdown elements programmatically. Finally, the Extracted Text section illustrates the output generated by TextContentRenderer, which removes all Markdown formatting and HTML-specific information to produce clean, plain text that can be used for search indexing, content previews, text analysis, or document summaries.

Generated HTML

<h1>Java Guide</h1>
<p>Welcome to <strong>CommonMark</strong>.</p>
<h2>Features</h2>
<ul>
<li>Parser</li>
<li>Renderer</li>
<li>Visitor</li>
</ul>

Heading Level : 1
Text : Java Guide

Text : Welcome to
Text : CommonMark

Heading Level : 2
Text : Features

Bullet List

List Item
Text : Parser

List Item
Text : Renderer

List Item
Text : Visitor

Extracted Text

Java Guide

Welcome to CommonMark.

Features

Parser

Renderer

Visitor

4. Customizing HTML Attributes Using AttributeProvider

One of the biggest advantages of commonmark-java is its highly customizable HTML rendering pipeline. Developers can override how individual Markdown nodes are rendered without modifying the parser itself. The following example adds a custom CSS class and security attributes to every generated hyperlink.

// CustomAttributeProviderExample.java
import java.util.Map;

import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;

public class CustomAttributeProviderExample {

    public static void main(String[] args) {
        String markdown = "# CommonMark Demo\n\n" + "Visit [OpenAI](https://openai.com) for more details.";

        // Parse Markdown
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdown);

        // Create custom HTML renderer
        HtmlRenderer renderer = HtmlRenderer.builder()
                .attributeProviderFactory(context ->
                        new AttributeProvider() {
                            @Override
                            public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
                                if (node instanceof Link) {
                                    attributes.put("class", "external-link");
                                    attributes.put("target", "_blank");
                                    attributes.put("rel", "noopener noreferrer");
                                }
                            }
                        })
                .build();

        // Generate HTML
        String html = renderer.render(document);
        System.out.println("Generated HTML");
        System.out.println(html);
    }
}

5. Customizing Node Rendering

The renderer also allows replacing the default rendering logic for selected node types by registering a custom node renderer. This is useful when you want to change how headings, links, images, code blocks, or other elements are emitted.

// CustomNodeRendererExample.java
import java.util.Collections;
import java.util.Set;

import org.commonmark.node.Heading;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.CoreHtmlNodeRenderer;
import org.commonmark.renderer.html.HtmlNodeRendererContext;
import org.commonmark.renderer.html.HtmlNodeRendererFactory;
import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.html.HtmlWriter;

public class CustomNodeRendererExample {

    public static void main(String[] args) {
        String markdown = "# Java Guide\n\n" + "Welcome to CommonMark.\n\n" + "## Features";

        // Parse Markdown
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdown);

        // Create custom HTML renderer
        HtmlRenderer renderer = HtmlRenderer.builder()
                .nodeRendererFactory(CustomHeadingRenderer::new)
                .build();

        // Render HTML
        String html = renderer.render(document);
        System.out.println("Generated HTML");
        System.out.println(html);
    }

    static class CustomHeadingRenderer extends CoreHtmlNodeRenderer {
        private final HtmlWriter html;

        public CustomHeadingRenderer(HtmlNodeRendererContext context) {
            super(context);
            this.html = context.getWriter();
        }

        @Override
        public Set<Class<? extends Node>> getNodeTypes() {
            return Collections.singleton(Heading.class);
        }

        @Override
        public void visit(Heading heading) {
            html.line();
            html.tag("section");
            html.tag("h" + heading.getLevel());
            visitChildren(heading);
            html.tag("/h" + heading.getLevel());
            html.tag("/section");
            html.line();
        }
    }
}

6. Conclusion

The commonmark-java library is a powerful and standards-compliant solution for processing Markdown in Java applications. Its parser builds a rich AST that can be traversed, analyzed, and transformed before rendering HTML, making it suitable for documentation platforms, blogging engines, content management systems, and developer tools. In addition to straightforward Markdown-to-HTML conversion, the library supports plain-text extraction, custom HTML attributes, and custom node renderers that allow developers to tailor the generated HTML to their application’s needs. While the core library does not provide HTML-to-Markdown conversion, it excels at Markdown parsing and rendering with excellent performance, extensibility, and adherence to the CommonMark specification.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button