Let's get started with a Microservice Architecture with Spring Cloud:
Markdown Rendering Using commonmark-java
Last updated: June 19, 2026
1. Overview
Manipulating Markdown content is a common programming task. CommonMark is a Java library that simplifies working with Markdown documents.
In this tutorial, we’ll learn how to manipulate Markdown content using the library. We’ll see how to parse Markdown into HTML and convert HTML back into Markdown. Finally, we’ll explore how to customize nodes for advanced processing.
2. commonmark-java Library
The commonmark-java library provides classes and interfaces for working with Markdown content based on the CommonMark specification. It allows us to parse Markdown into HTML and convert HTML back into Markdown. Additionally, it provides access to the Abstract Syntax Tree (AST), enabling further customization and processing.
To use the common-java library, let’s add the commonmark dependency to our pom.xml:
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.28.0</version>
</dependency>
The commark dependency provides classes such as Parser, HtmlRenderer, and MarkdownRenderer for Markdown processing and rendering.
Additionally, CommonMark provides extension dependencies for more advanced processing features. Examples include commonmark-ext-gfm-tables for GitHub Flavored Markdown tables and commonmark-ext-gfm-alerts for alert blocks.
3. Parsing and Rendering Markdown to HTML
Moving on, let’s see one of the most common uses of the library: parsing Markdown and rendering it as HTML.
First, let’s define a method named markDownToHtml():
public static String markDownToHtml(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
return renderer.render(document);
}
Here, we create an instance of Parser to parse Markdown input into a document node. Next, we create an HtmlRenderer instance to render the parsed node as HTML.
A Node represents an element in the parsed Markdown document tree.
Then, let’s write a unit test to verify the result:
@Test
void givenMarkdownInput_whenConvertingToHtml_thenReturnRenderedHtml() {
String html = markDownToHtml("Welcome to *Baeldung*");
assertEquals("<p>Welcome to <em>Baeldung</em></p>\n", html);
}
In the code above, we pass a string containing Markdown syntax to markDownToHtml() method. Since Baeldung is wrapped in asterisks (*), the Markdown parser interprets it as emphasized text. Consequently, the renderer converts it to an HTML <em> element.
4. Processing Parsed Nodes
Furthermore, we can use a visitor to further process nodes in the parsed document tree. The library allows us to extend the AbstractVisitor class to process a node.
Let’s create a visitor class that counts every word in a sentence:
class WordCountVisitor extends AbstractVisitor {
int wordCount = 0;
@Override
public void visit(Text text) {
wordCount += text.getLiteral().split("\\w+").length;
visitChildren(text);
}
}
Next, let’s write a method that uses the visitor:
public static int processParsedNode(String markdown) {
Parser parser = Parser.builder().build();
Node node = parser.parse(markdown);
WordCountVisitor visitor = new WordCountVisitor();
node.accept(visitor);
return visitor.wordCount;
}
In the method above, we create a WordCountVisitor object and pass it to the parsed node for processing.
Let’s write a unit test to confirm the method:
@Test
void givenMarkdownInput_whenProcessingParsedNode_thenReturnWordCount() {
int wordCount = processParsedNode("Welcome to *Baeldung*");
assertEquals(3, wordCount);
}
Here, we verify that the expected word count is equal to the actual word count.
5. Rendering HTML to Markdown
Furthermore, CommonMark also provides classes for rendering HTML-like document structures into Markdown format, making it a library for both HTML and Markdown processing.
Let’s see this in action by writing code that converts an HTML heading into a Markdown format:
public static String htmlToMarkDown(String htmlHeading) {
Heading heading = new Heading();
heading.setLevel(2);
heading.appendChild(new Text(htmlHeading));
Document document = new Document();
document.appendChild(heading);
MarkdownRenderer renderer = MarkdownRenderer.builder()
.build();
return renderer.render(document);
}
In the code above, we create a Heading object and set the level to 2, which represents an H2 heading. Next, we append a Text object containing the heading content. Finally, we use the MarkdownRenderer builder to render the document as Markdown.
Next, let’s write a unit test to confirm the output:
@Test
void givenHeadingText_whenConvertingToMarkdown_thenReturnMarkdownHeading() {
String markdown = htmlToMarkDown("Java Tutorial");
assertEquals("## Java Tutorial\n", markdown);
}
In the code above, we verify that the renderer correctly converts the document structure into a valid Markdown output.
6. Customizing HTML Rendering
Furthermore, CommonMark allows us to customize rendered HTML attributes using the AttributeProvider interface.
Let’s see this in action by implementing a class custom image attribute provider:
public class ImageAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
if (node instanceof Image) {
attributes.put("class", "border");
}
}
}
In the code above, we create a class named ImageAttributeProvider that implements the AttributeProvider interface. Inside the setAttributes() method, we check whether the current node is an instance of Image. If it is, we add a class attribute with the value “border“.
Next, let’s write a method that applies the custom attribute provider during HTML rendering:
public static String changingHtmlAttribute(String source) {
Parser parser = Parser.builder()
.build();
Node node = parser.parse(source);
HtmlRenderer renderer = HtmlRenderer.builder()
.attributeProviderFactory(context -> new ImageAttributeProvider())
.build();
return renderer.render(node);
}
In the code above, we customize the HtmlRenderer with a custom attribute provider using the attributeProviderFactory() method. Using lambda expressions, we invoke our provider for each node, allowing us to customize the generated HTML attributes. Every rendered image element receives a class=”border” attribute.
Finally, let’s write a unit test to ascertain the customized output:
@Test
void givenImageMarkdown_whenRenderingHtml_thenAddCustomClassAttribute() {
String html = changingHtmlAttribute("");
assertEquals("<p><img src=\"/url.png\" alt=\"text\" class=\"border\" /></p>\n", html);
}
In the test above, we verify that the custom attribute provider successfully adds the border CSS class to rendered image elements.
7. Customizing Render Node
Moreover, the library allows us to customize how specific nodes are rendered by implementing the NodeRenderer interface.
Let’s create a custom renderer for IndentedCodeBlock nodes:
public class IndentedCodeBlockNodeRenderer implements NodeRenderer {
private final HtmlWriter html;
public IndentedCodeBlockNodeRenderer(HtmlNodeRendererContext context) {
this.html = context.getWriter();
}
@Override
public Set<Class<? extends Node>> getNodeTypes() {
return Set.of(IndentedCodeBlock.class);
}
@Override
public void render(Node node) {
IndentedCodeBlock codeBlock = (IndentedCodeBlock) node;
html.line();
html.tag("pre");
html.text(codeBlock.getLiteral());
html.tag("/pre");
html.line();
}
}
In the code above, we implement the NodeRenderer interface and specify that the renderer handles IndentedCodeBlock nodes through the getNodeTypes() method.
Then, in the render() method, we manually generate the HTML output for the code block using HtmlWriter.
Next, let’s register the custom renderer with HtmlRenderer:
public static String customizingHtmlRendering(String source) {
Parser parser = Parser.builder()
.build();
Node node = parser.parse(source);
HtmlRenderer renderer = HtmlRenderer.builder()
.nodeRendererFactory(IndentedCodeBlockNodeRenderer::new)
.build();
return renderer.render(node);
}
Here, we customize the renderer using the nodeRendererFactory() method. We invoke the custom node using a method reference. This allows the renderer to delegate matching nodes to our custom implementation during HTML generation
Let’s write a unit test to verify the customizingHtmlRendering() method:
@Test
void givenIndentedCodeBlock_whenRenderingHtml_thenUseCustomNodeRenderer() {
String html = customizingHtmlRendering("Example:\n\n code");
assertEquals("<p>Example:</p>\n<pre>code\n</pre>\n", html);
}
Here, we verify that indented code blocks are rendered using our custom renderer implementation.
8. Conclusion
In this article, we learned how to use the CommonMark library to parse Markdown into HTML and convert HTML back into Markdown. Additionally, we saw how to customize HTML attributes and node rendering for more advanced processing scenarios.
As always, the source code for the sample code is available over on GitHub.
















