Yesterday, December 14th, we officially released office-stamper v3.0.0. While
the version number changed by just one digit, the architectural overhaul under
the hood is the most significant since I forked the project.
Today, I want to dive into the core of this release: the move to a hierarchical context system.
The Thesis: Predictability is King
In a template engine, the most “magical” part is often how expressions (SpEL, in our case) find their data. In v2.x, this was a bit of a “flat” affair. While it worked for simple documents, complex templates with nested repeaters often struggled with variable shadowing and scope leakage.
For v3, I had one goal: make the resolution of expressions strictly predictable.
The Technique: ContextTree and ContextBranch
Instead of a single root object, v3 uses a ContextTree. Every time the engine
enters a nested scope (like a repeat loop), it pushes a new ContextBranch onto
a stack.
When an expression like ${item.name} is evaluated, the engine doesn’t just
look at the current item. It looks down the stack of branches, from the most
specific (the current loop item) to the most general (the document root).
// Conceptual v3 resolution flow
var contextTree = new ContextRoot(contextRoot);
// ... inside a loop ...
var branch = contextTree.push(currentItem);
// SpEL can now see currentItem, AND anything from the outer scopes.
Worked Example: The Nested Loop Problem
Imagine an invoice template where you have a list of Categories, and inside
each category, a list of Items. In v2.x, if you wanted to access the
category.total from inside the item loop, you often had to resort to messy
workarounds.
In v3, this is native. The item branch is a child of the category branch.
SpEL naturally resolves the variables by walking up the tree. Shadowing works
exactly as you’d expect: if both objects have a name property, the item.name
wins inside the loop, but you can still reach out to the parent if needed.
EvaluationContextFactory: Fresh State Every Time
To complement the hierarchical context, I’ve replaced the old
EvaluationContextConfigurer with an EvaluationContextFactory.
Instead of mutating a shared, long-lived context (which was a source of subtle
state bugs), the engine now creates a completely fresh EvaluationContext
for every single comment or placeholder “Hook”.
main() {
// v3 Factory pattern
config.setEvaluationContextFactory(root -> {
var ctx = new StandardEvaluationContext(root);
// ... custom setup ...
return ctx;
});
}
This ensures that no side effects from one part of the document can leak into another. It is “clean room” processing for every expression.
Pitfalls & Migration
The biggest pitfall is, of course, the breaking change. Moving to document-order processing and a stack-based context means that some templates might behave differently if they relied on the “accidental” resolution order of v2.x.
Additionally, the OfficeStamper interface has been made more functional.
Instead of taking an OutputStream and performing side effect I/O, the stamp
method now returns the stamped document directly. This simplifies
post-processing and testing, though it requires an extra step if you want to
save to a stream immediately.
As a solo maintainer, I’m concerned about the friction this causes for my users. But for the long-term health of the library—and for my own goals of handling complex business logic, this migration was unavoidable.
Checklist for v3 Migration
If you are upgrading this week:
- Update imports: Core interfaces moved to
pro.verron.officestamper.api. - Update
stampcalls: The method now returns the document; useStreamStamperif you need the old I/O behavior. - Switch to Factory: Replace
EvaluationContextConfigurerwithEvaluationContextFactory. - Test Nested Scopes: Verify your repeaters and conditional comments.
- Check Order: If you have comments and placeholders on the same line, verify they still resolve in the order you expect.
Final Thoughts
Shipping v3 is a relief. It is the version of office-stamper I’ve wanted to
build since I took over the original Docx-Stamper project. It sheds the
technical debt of the original fork and establishes a professional, predictable
foundation for the future.
December 15th 2025 — Release Summary:
- Major: Released
office-stamperv3.0.0. - Feat: Implemented hierarchical
ContextTreefor SpEL resolution. - API: Introduced
EvaluationContextFactoryfor better isolation. - Breaking: Consolidated public API into
.apipackage. - Refactor: Strict document-order processing via
DocxIterator.