A framework-neutral Thymeleaf-compatible dynamic template engine for Rust
Overview · Maturity · Workspace · Quick Start · Quality · Roadmap
Version:
0.1.0-alpha.1· MSRV: Rust 1.95 · Edition: 2024 · Resolver: 3Upstream baseline: Thymeleaf
3.1.5.RELEASE@10f9dd2e
thymeleaf-rust is a behavioral port of Java Thymeleaf 3.1.5 to Rust. It parses and renders HTML/XML/TEXT templates with expression evaluation, dialect processors, caching, and a framework-neutral web contract. The project does not depend on the JVM at runtime — all Java semantics are re-implemented in pure Rust.
| Claim | Value | Evidence |
|---|---|---|
| Semantic parity corpus | 2,608 .thtest cases, 2,595 behaviorally identical |
CI thtest_upstream_plain_batch |
| Object-level coverage | 491 main objects, 4,291 methods, 0 missing | cargo xtask migration-check |
| Source-parity ledger | 413 core test entries (Spring excluded by policy) | source_parity_inventory.json |
| Acceptance gate | 2,686 assets SHA-256 verified | thymeleaf-test/tests/acceptance.rs |
| Tests | 295 (lib) + 964 (integration) + 45 (adapters) | cargo test --workspace |
| CI platforms | ubuntu-latest, macos-latest | GitHub Actions matrix |
| unsafe | forbid across all crates |
[lints.rust] unsafe_code = "forbid" |
- No JVM/bytecode interop at runtime (Java is only the behavior oracle).
- No Spring/JSP/Servlet runtime integration (Java-only modules excluded by policy).
- No 1:1 internal implementation copy — Java idioms are mapped to Rust ownership/trait/error patterns.
| Feature | Status | Crate | Limitation |
|---|---|---|---|
| HTML/XML/TEXT template parsing | ✅ Stable | thymeleaf |
html5gum tokenizer has pathological-input memory risk |
| Expression evaluation (OGNL subset) | ✅ Stable | thymeleaf |
No JVM reflection; ACL-gated static method whitelist |
| Standard dialect processors | ✅ Stable | thymeleaf |
2,608 corpus cases verified |
| Template cache & resolvers | ✅ Stable | thymeleaf |
String/File/Class/URL/Multi/ByteArray loaders |
| Auto-escaping & output formats | ✅ Stable | thymeleaf |
HTML/XML/JavaScript/CSS/JSON/RTF/PlainText |
| Decoupled template logic | ✅ Stable | thymeleaf |
.th.xml sidecar |
| Framework-neutral web contract | ✅ Stable | thymeleaf |
IWebExchange / IWebRequest / IWebSession |
| Framework adapters (P0) | ✅ Stable | thymeleaf-axum/thymeleaf-actix-web/thymeleaf-topcoat |
Full IWebExchange suite + contract tests aligned with hyper benchmark + render_async |
| Framework adapters (thin) | 🧪 Preview | hyper/tower/ntex/poem/rocket/salvo/tonic | Response-level adapters; IWebExchange via hyper benchmark |
| Framework adapters (experimental) | 🧪 Experimental | gotham/tide/warp | Upstream stalled/replaced; best-effort, tests behind full feature |
| sa-token security dialect | 🧪 Preview | thymeleaf-sa-token |
12 contract tests |
| Vernal view layer | 🧪 Preview | thymeleaf-vernal |
ThymeleafView → vernal HttpResponse; ViewResolver bridge planned |
| Fuzz (property tests) | 🚧 Partial | thymeleaf-test |
XML/TEXT parser proptest; HTML/render excluded (see Known Limitations) |
| Dimension | Scope | Method |
|---|---|---|
| Behavioral | 2,595 / 2,608 executable cases match Java byte-for-byte | Corpus differential |
| Policy differences | 13 cases (12 execinfo upstream-disabled + 1 arbitrary reflection chain) |
Named disposition |
| Source parity | 413 core Java test classes tracked (Spring excluded) | source_parity_inventory.json |
| Object parity | 491 / 491 main objects, 4,291 / 4,291 methods | migration-check |
| Item | Value |
|---|---|
| MSRV | 1.95 |
| Edition | 2024 |
| Resolver | 3 |
| Clippy | -D warnings |
| rustfmt | stable |
| unsafe | forbid (all crates) |
| missing_docs | deny (thymeleaf crate) |
[Downstream crate]
│ cargo add thymeleaf / thymeleaf-<framework>
▼
┌──────────────────────────────────────────────────────────┐
│ thymeleaf-rust Workspace │
│ │
│ thymeleaf Core engine, public API, web │
│ thymeleaf-test Java parity corpus, golden tests │
│ thymeleaf-examples GTVG sample port │
│ thymeleaf-support/* 15 framework adapters │
│ ├── thymeleaf-actix-web thymeleaf-axum │
│ ├── thymeleaf-hyper thymeleaf-rocket │
│ ├── thymeleaf-sa-token thymeleaf-salvo ... │
├──────────────────────────────────────────────────────────┤
│ xtask migration-check tool │
│ scripts/ golden regeneration, audit │
│ docs/ migration docs, release policy │
└──────────────────────────────────────────────────────────┘
| Crate | Publish | Role |
|---|---|---|
thymeleaf |
✅ | Core engine |
thymeleaf-actix-web |
✅ | Actix-web adapter |
thymeleaf-axum |
✅ | Axum adapter |
thymeleaf-gotham |
✅ | Gotham adapter |
thymeleaf-hyper |
✅ | Hyper adapter |
thymeleaf-ntex |
✅ | Ntex adapter |
thymeleaf-poem |
✅ | Poem adapter |
thymeleaf-rocket |
✅ | Rocket adapter |
thymeleaf-sa-token |
✅ | Sa-Token security dialect |
thymeleaf-salvo |
✅ | Salvo adapter |
thymeleaf-tonic |
✅ | Tonic adapter |
thymeleaf-topcoat |
✅ | Topcoat adapter |
thymeleaf-tower |
✅ | Tower adapter |
thymeleaf-warp |
✅ | Warp adapter |
thymeleaf-tide |
❌ | Tide adapter (unmaintained upstream) |
thymeleaf-vernal |
❌ | Vernal adapter (git deps, pending crates.io) |
thymeleaf-test |
❌ | Test harness (internal) |
thymeleaf-examples |
❌ | Examples (internal) |
Expression evaluation defaults to a read-only safe subset:
restrict_external_access = trueby default —new,param,@Type@syntax blocked.- Arbitrary classes and reflection blocked — 10 blocked package prefixes (
java./javax./jakarta./jdk./…), 53 allowed classes (wrappers, collections, time, math). - Restricted static method whitelist —
Math.abs/sqrt/…,Integer.parseInt,LocalDateTime.of,String.formaton 9 classes; all others rejected byThymeleafACLClassResolver. unsafe_code = "forbid"across all crates — zero unsafe in workspace source.- Hosts can further restrict via
OgnlRuntime(opt-in).
[dependencies]
thymeleaf = { git = "https://github.com/easy-4-rust/thymeleaf-rust.git" }use thymeleaf::{TemplateEngine, TemplateMode};
use thymeleaf::context::Context;
use thymeleaf::templateresolver::StringTemplateResolver;
use thymeleaf::ITemplateResolver;
use std::sync::Arc;
fn main() {
let mut resolver = StringTemplateResolver::new();
resolver.set_template_mode(TemplateMode::HTML);
let engine = TemplateEngine::new();
engine.set_template_resolver(Arc::new(resolver) as Arc<dyn ITemplateResolver>)
.expect("resolver");
let ctx = Context::new();
let output = engine.process_template("<p th:text=\"${msg}\">fallback</p>", &ctx)
.expect("render");
println!("{}", output.to_string_lossy());
}| Java | Rust | Reason |
|---|---|---|
| Checked exceptions | Result<T, E> + thiserror enums |
Explicit error propagation |
null |
Option<T> |
Null safety |
synchronized / ConcurrentHashMap |
Arc<RwLock<_>> / DashMap-style |
Ownership-based concurrency |
Reflection / Class.forName |
ThymeleafACLClassResolver + OgnlRuntime trait |
No dynamic class loading; ACL-gated |
| Inner classes | Same-file types (audit-approved type families) | Rust module conventions |
ExecutorService |
futures + synchronous core |
Core is sync; async at adapter layer |
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo xtask migration-check --upstream <upstream> --baseline 10f9dd2e...
python3 scripts/audit_migration_layout.py --fail-on-warning # layout audit 0/0/0
cargo deny check # licenses/bans/sources/advisories
cargo audit # vulnerabilities
cargo llvm-cov --workspace --all-features --summary-only
THYMELEAF_SCOPE=semantic_all cargo test -p thymeleaf-test --test thtest_upstream_plain_batch # 2,608 corpus| Type | Count | Purpose |
|---|---|---|
| Unit (lib) | 295 | Core logic |
| Integration (parity) | 964 | Java 1:1 differential |
| Adapter contracts | 45 | Framework integration smoke |
| Corpus | 2,608 | Upstream behavioral parity |
| Acceptance | 2,686 assets | SHA-256 byte-identical |
| Fuzz (proptest) | 2 active | XML/TEXT parser robustness |
- html5gum tokenizer: Pathological Unicode input (isolated surrogates, special sequences) can cause internal memory inflation. HTML parser fuzz excluded; robustness covered by 2,608 corpus.
- Render smoke proptest: Random expression injection can cause
process_templatetimeout (>60s). Excluded; covered by corpus + workspace tests. - API baseline CI:
cargo public-apirequires nightly; CI uses stable →continue-on-error(alpha stage).
| Phase | Status | Items |
|---|---|---|
| Semantic alignment | ✅ Done | 2,608 corpus, 491 objects, 4,291 methods |
| Governance audit | ✅ Done | strict blockers 0, warnings 0, CI enforced |
| Fuzz OOM fix | ✅ Done | DiscardingWriter + shrink clamp + serial |
| Release ecosystem | 🚧 In progress | cargo package --verify, docs.rs, adapter contracts |
| Version 0.1.0 | 🗓️ Planned | API freeze, CHANGELOG, tag |
| Benchmark suite | 🗓️ Planned | Criterion render/parse/expression |
Run the basic gates before submitting:
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-featuresNew public APIs must include docs, tests, and SemVer/MSRV impact notes.
This project ports behavior from Thymeleaf (Apache 2.0).
Upstream license, source commit, and modification scope are documented in docs/.
Back to top · Actions · Issues