HTML Builder Benchmark

May 15, 2026 ยท View on GitHub

This project compares the performance and memory efficiency of several Java HTML generation frameworks and template engines.

Environment

  • Java Version: 25.0.2 (java-25-amazon-corretto)
  • Hardware/Memory: ~15 GiB total RAM available (tested on a machine with 15 GiB RAM)
  • Operation System: Ubuntu 24.04.4 LTS

Tested Frameworks

  • j2html: 1.6.0
  • HtmlFlow: 5.0.3
  • JTE (Java Template Engine): 3.1.12
  • Dom4j: 2.2.0
  • Jsoup: 1.22.1
  • Kotlinx.html: 0.12.0
  • Ujorm3: 3.0.0
  • StringBuilder: (Native JDK baseline)

Test Scenarios & Metrics

The benchmark is powered by JMH (Java Microbenchmark Harness) configured with the -prof gc profiler.

The current comparison focuses on two scalable simple scenarios:

  • SIMPLE_100 - simple HTML page with a table of 100 rows.
  • SIMPLE_1000 - same page shape, but with 1000 rows.

Special HTML-sensitive characters are injected into roughly 5% of text content in all scenarios to keep escaping behavior representative of production input.

To simulate real-world web server behavior, the benchmark streams output directly to a sink (JMH Blackhole) instead of building one giant in-memory string. The table columns represent:

  • Throughput [ops/s]: The number of HTML generation operations completed per second. Measured in JMH Throughput mode. Higher is better.
  • Allocation [B/op] (Normalized Allocation Rate): The exact amount of temporary Heap memory allocated in Bytes per operation. Measured via JMH GC profiler. Lower allocation means significantly less pressure on the Garbage Collector, resulting in lower CPU usage and fewer latency spikes. Lower is better.
  • JAR Size [kB]: The total file footprint of the framework dependency artifact. Lower is better.
  • Quality [0-100]: An evaluation of code cleanliness, type safety, boilerplate reduction, and estimated maintenance/refactoring costs. Higher is better. Note: This assessment follows the weighting recommended by Gemini Pro and is based on the benchmark implementations, official framework docs, and latest benchmark outcomes.
  • Overall Score [%]: A weighted, normalized aggregate score across performance and quality metrics (excluding JAR size), representing overall attractiveness for common developers. Weights: SIMPLE_100 Throughput 20%, SIMPLE_1000 Throughput 20%, SIMPLE_100 Allocation 10%, SIMPLE_1000 Allocation 10%, Quality 40%. Higher is better.

Note: The Quality assessment and explanatory paragraph are AI-generated by Codex 5.3.

How Quality was scored

The Quality score is a weighted estimate (not a direct runtime metric). It combines:

  • Maintenance/refactoring cost (30%) - expected effort to evolve schemas, queries, and domain mappings safely over time.
  • Code cleanliness & API ergonomics (30%) - readability of benchmark scenarios, verbosity, and clarity of intent.
  • Boilerplate reduction (20%) - amount of repetitive setup and mapping code needed per scenario.
  • Type safety (20%) - compile-time guarantees in query construction, mapping, and update operations.

Performance and memory results from this benchmark are used as secondary calibration signals, not as the primary scoring axis for Quality.

Benchmark Results

The libraries are sorted by a weighted Overall Score (highest to lowest). Values are rounded to whole numbers. The exact Overall Score weights are defined in the metrics section above.

LibrarySIMPLE_100 Throughput [ops/s]SIMPLE_100 Allocation [B/op]SIMPLE_1000 Throughput [ops/s]SIMPLE_1000 Allocation [B/op]JAR Size [kB]Quality [0-100]Overall Score [%]
Jte33,8307,2003,53850,4027990100.0
KotlinxHtml12,20567,8731,268627,3198258865.0
UjormElement10,20328,8411,060244,1281248662.0
J2html11,460124,6571,0861,229,8221988357.6
Jsoup13,499143,1931,6361,290,7184966448.6
StringBuilder (baseline)24,98994,1522,855845,85903546.0
HtmlFlow12,512646,7531,3375,246,890527639.1
Dom4j6,365166,5536471,616,5893245428.3

Short rationale: after reviewing all available benchmark implementations (including *Renderer classes, JTE templates, and helpers), the same metric weights were applied and scores were normalized to better reflect long-term maintainability. Template/DSL approaches rank higher due to readability and safer refactoring; general DOM APIs rank lower, and manual generation ranks lowest.

  • Jte (90) - very clean templates, low boilerplate, strong long-term maintainability, and robust template compilation.
  • KotlinxHtml (88) - type-safe DSL and good readability, with slightly higher complexity due to the Kotlin/JVM layer in a Java-focused benchmark.
  • UjormElement (86) - consistent fluent API, good escaping behavior, and shared rendering logic, with slightly more verbosity than template-based approaches.
  • J2html (83) - readable Java DSL and decent type safety, but nesting depth and call volume grow quickly in larger trees.
  • HtmlFlow (76) - type-safe API and strong streaming model, but lower ergonomics due to frequent .__() and callback-heavy style.
  • Jsoup (64) - robust and widely known DOM API, but more manual tree construction and higher maintenance overhead for view code.
  • Dom4j (54) - XML/DOM approach is less ergonomic for HTML templating, with more boilerplate and weaker refactoring ergonomics.
  • StringBuilder (35) - even with a safe helper, manual tag and structure management remains the riskiest option for long-term development.

Evaluation

Jte delivers the strongest overall performance profile in both tested sizes, combining high throughput with very low allocation. UjormElement remains competitive in throughput and stands out with strong memory discipline among Java builder-style APIs, especially compared to most DOM-based or callback-heavy alternatives. StringBuilder is still a useful raw baseline, but it trades away API safety and maintainability for speed.


Learn more about the Ujorm3 framework.