swagger-api / swagger-api/swagger-codegen
Slow generation: HandlebarTemplateEngine rebuilds Handlebars and reparses templates on every render
Nobody has claimed this yet.
- Dominant language
- Mustache
- Stars
- 17.8k
- Forks
- 6k
- PR merge metrics
- No merged PRs in 30d
Description
Summary
HandlebarTemplateEngine.getRendered(...) creates a new Handlebars instance and recompiles the template (and every partial it references) on every call. Profiling shows the jknack ANTLR-based lexer accounting for ~65% of CPU during code generation for a typical OpenAPI specification.
How to reproduce
Generate code with the handlebars engine (the default for v3) against any sufficiently large spec. The cost grows roughly with (templates_rendered) × (partials_per_template) because every render repeats the full Handlebars setup + parse.
Profile
Java Flight Recorder captured during a representative warm run shows:
- ~65% of in-method CPU under
org.antlr.v4.runtime.atn.LexerATNSimulator.closure(...)— jknack's parser. - Each
getRenderedallocates a newHandlebars, re-registers all helpers, and reparses the entry template, which in turn re-parses every{{> partial }}it transitively includes.
Why it matters
Generating a server-side stub project with ~75 controllers / ~180 output files takes ~50–60 s on the affected code path. Once compiled templates are cached for the lifetime of a single generation, the same workload completes in ~12 s — a ~4.5× speed-up with no API change and no spec-side change.
Precedent
#12313 / its accompanying fix established the same pattern in another hotspot (AbstractJavaCodegen.toModelName() was called millions of times and was cached for a 3× improvement). The fix proposed here applies the same idea to template parsing.
Caveat / why a naive cache breaks output
jknack's stock ConcurrentMapTemplateCache cannot be used as-is for handlebars partials in prettyPrint(true) mode. Partial.merge wraps the partial's TemplateSource in an anonymous class whose equals/hashCode delegate only to the underlying source (filename + lastModified), but whose content() returns the partial body re-indented by the include site's leading whitespace. The same partial included at two different indents therefore collides on lookup and the first-compiled indent wins for every subsequent include — observable as silently shifted whitespace in generated output. See related jknack issues #401 and #708.
The proposed fix carries a small replacement cache (~95 LOC) keyed on (filename, content), which preserves the speed-up while keeping per-indent renders correct.
Proposed fix
A PR will follow that:
- Holds a single
Handlebarsinstance perHandlebarTemplateEngine(and so per generation), registering helpers once. - Caches compiled top-level
Templates pertemplateFile. - Installs an
IndentAwareTemplateCacheto make handlebars' own partial cache safe underprettyPrint(true).
No public API changes. No template changes. No swagger-codegen-generators change required.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at HandlebarTemplateEngine.getRendered(...) and inspect how the Handlebars instance, helpers, templates, and partials are created for each render. Review the proposed IndentAwareTemplateCache behavior and verify both compiled-template reuse and correct prettyPrint(true) output at different include indents. Done means preserving output with no public API or template changes while achieving the stated generation speed-up.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- performance, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100