High-performance HTML to Markdown converter with Java Panama FFI bindings to the Rust core. Uses Foreign Function & Memory API for zero-dependency, thread-safe conversion with full metadata extraction support.
- Same renderer as every binding — output matches Rust, Python, Node.js, Ruby, PHP, Go, Java, .NET, Elixir, R, Dart, Swift, Zig, C FFI, and WASM.
- Structured conversion result — Markdown plus metadata, links, headings, images, tables, and warnings where the binding exposes them.
- Production defaults — HTML is parsed with the Rust core, sanitized by default, and rendered without runtime-specific Markdown drift.
- Java package — Panama FFM binding for direct native calls without a sidecar process.
<dependency>
<groupId>io.xberg</groupId>
<artifactId>html-to-markdown</artifactId>
<version>3.8.2</version>
</dependency>Requires Java 25+ with Panama FFI support.
Maven:
<dependency>
<groupId>io.xberg</groupId>
<artifactId>html-to-markdown</artifactId>
<version>3.8.2</version>
</dependency>Gradle (Kotlin DSL):
implementation("io.xberg:html-to-markdown:3.8.2")Apple M4 · convert() · Real Wikipedia documents
| Document | Size | Latency | Throughput |
|---|---|---|---|
| Lists (Timeline) | 129KB | 291.5 MB/s | |
| Tables (Countries) | 360KB | 272.0 MB/s | |
| Mixed (Python) | 656KB | 258.5 MB/s |
Basic conversion:
import io.xberg.htmltomarkdown.HtmlToMarkdown;
import io.xberg.htmltomarkdown.ConversionResult;
public class Example {
public static void main(String[] args) {
String html = "<h1>Hello World</h1><p>This is a <strong>test</strong>.</p>";
ConversionResult result = HtmlToMarkdown.convert(html);
System.out.println(result.content());
}
}With conversion options:
import io.xberg.htmltomarkdown.HtmlToMarkdown;
import io.xberg.htmltomarkdown.ConversionOptions;
import io.xberg.htmltomarkdown.ConversionResult;
public class MetadataExample {
public static void main(String[] args) {
String html = "<html><head><title>My Page</title></head>"
+ "<body><h1>Welcome</h1><a href=\"https://example.com\">Link</a></body></html>";
ConversionOptions options = ConversionOptions.builder()
.extractMetadata(true)
.build();
ConversionResult result = HtmlToMarkdown.convert(html, options);
System.out.println("Markdown: " + result.content());
System.out.println("Title: " + result.metadata().document().title());
System.out.println("Headers: " + result.metadata().headers().size());
System.out.println("Links: " + result.metadata().links().size());
}
}The converter routes each input through one of three tiers based on a fast prescan of the byte stream:
- Tier-1 — single-pass byte scanner. Handles 110+ HTML tags directly. Bails on any construct it cannot prove byte-equivalent to Tier-2.
- Tier-2 — DOM walker. Picks up Tier-1 bails and inputs the classifier rejected up front.
- Tier-3 — standards-conformant parser. Engaged for malformed HTML requiring full HTML5 repair.
The dispatcher is invisible to the caller. Output is byte-identical across tiers — enforced by a 116-snapshot oracle.
- 16 languages, one Rust core. Rust, Python, Node.js, WASM, Java, Go, C#, PHP, Ruby, Elixir, R, Dart, Kotlin (Android), Swift, Zig, C ABI.
- CommonMark-compatible Markdown with GFM-style tables.
- Djot output: set
output_format = "djot"(see Djot Output Format section below). - Real-HTML robust: unclosed tags, CDATA, custom elements, malformed entities, nested tables, mixed encodings handled without losing content.
- Metadata extraction, visitor API, inline images, configurable preprocessing presets.
- Per-group regression gates in CI: every PR runs the bench harness against per-group thresholds.
HtmlToMarkdown.convert(String html) : ConversionResult
HtmlToMarkdown.convert(String html, ConversionOptions options) : ConversionResult
Converts HTML to Markdown. Returns a ConversionResult record with all results in a single call.
ConversionResult result = HtmlToMarkdown.convert(html);
String markdown = result.content(); // Converted Markdown string
HtmlMetadata metadata = result.metadata();
List<TableData> tables = result.tables();ConversionOptions – Key configuration fields:
heading_style: Heading format ("underlined"|"atx"|"atx_closed") — default:"atx"list_indent_width: Spaces per indent level — default:2bullets: Bullet characters cycle — default:"-*+"wrap: Enable text wrapping — default:falsewrap_width: Wrap at column — default:80code_language: Default fenced code block language — default: noneextract_metadata: Enable metadata extraction intoresult.metadata— default:trueoutput_format: Output markup format ("markdown"|"djot"|"plain") — default:"markdown"
The library supports converting HTML to Djot, a lightweight markup language similar to Markdown but with a different syntax for some elements. Set output_format to "djot" to use this format.
| Element | Markdown | Djot |
|---|---|---|
| Strong | **text** |
*text* |
| Emphasis | *text* |
_text_ |
| Strikethrough | ~~text~~ |
{-text-} |
| Inserted/Added | N/A | {+text+} |
| Highlighted | N/A | {=text=} |
| Subscript | N/A | ~text~ |
| Superscript | N/A | ^text^ |
import io.xberg.htmltomarkdown.HtmlToMarkdown;
import io.xberg.htmltomarkdown.ConversionOptions;
import io.xberg.htmltomarkdown.OutputFormat;
String html = "<p>This is <strong>bold</strong> and <em>italic</em> text.</p>";
// Default Markdown output
String markdown = HtmlToMarkdown.convert(html).content();
// Result: "This is **bold** and *italic* text."
// Djot output
String djot = HtmlToMarkdown.convert(html,
ConversionOptions.builder()
.withOutputFormat(OutputFormat.Djot)
.build()
).content();
// Result: "This is *bold* and _italic_ text."Djot's extended syntax allows you to express more semantic meaning in lightweight text, making it useful for documents that require strikethrough, insertion tracking, or mathematical notation.
Set output_format to "plain" to strip all markup and return only visible text. This bypasses the Markdown conversion pipeline entirely for maximum speed.
import io.xberg.htmltomarkdown.HtmlToMarkdown;
import io.xberg.htmltomarkdown.ConversionOptions;
import io.xberg.htmltomarkdown.OutputFormat;
String html = "<h1>Title</h1><p>This is <strong>bold</strong> and <em>italic</em> text.</p>";
String plain = HtmlToMarkdown.convert(html,
ConversionOptions.builder()
.withOutputFormat(OutputFormat.Plain)
.build()
).content();
// Result: "Title\n\nThis is bold and italic text."Plain text mode is useful for search indexing, text extraction, and feeding content to LLMs.
The visitor pattern enables custom HTML→Markdown conversion logic by providing callbacks for specific HTML elements during traversal. Pass a visitor as the third argument to convert().
Use Cases:
- Custom Markdown dialects – Convert to Obsidian, Notion, or other flavors
- Content filtering – Remove tracking pixels, ads, or unwanted elements
- URL rewriting – Rewrite CDN URLs, add query parameters, validate links
- Accessibility validation – Check alt text, heading hierarchy, link text
- Analytics – Track element usage, link destinations, image sources
Supported Visitor Methods: 40+ callbacks for text, inline elements, links, images, headings, lists, blocks, and tables.
- GitHub: github.com/xberg-io/html-to-markdown
- Maven Central: central.sonatype.com/artifact/io.xberg/html-to-markdown
- Discord: discord.gg/xt9WY3GnKR
- Xberg — document intelligence: text, tables, metadata from 91+ formats with optional OCR.
- Xberg Enterprise — managed extraction API with SDKs, dashboards, and observability.
- crawlberg — web crawling and scraping with HTML→Markdown and headless-Chrome fallback.
- html-to-markdown — fast, lossless HTML→Markdown engine.
- liter-llm — universal LLM API client with native bindings for 14 languages and 143 providers.
- tree-sitter-language-pack — tree-sitter grammars and code-intelligence primitives.
- alef — the polyglot binding generator that produces every per-language binding across the 5 polyglot repos.
We welcome contributions! Please see our Contributing Guide for details on:
- Setting up the development environment
- Running tests locally
- Submitting pull requests
- Reporting issues
All contributions must follow our code quality standards (enforced via pre-commit hooks):
- Proper test coverage (Rust 95%+, language bindings 80%+)
- Formatting and linting checks
- Documentation for public APIs
MIT License – see LICENSE. Copyright © Kreuzberg, Inc.
If you find this library useful, consider sponsoring the project.
Have questions or run into issues? We're here to help:
- GitHub Issues: github.com/xberg-io/html-to-markdown/issues
- Discord Community: discord.gg/xt9WY3GnKR