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
getRendered allocates a new Handlebars, 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
Handlebars instance per HandlebarTemplateEngine (and so per generation), registering helpers once.
- Caches compiled top-level
Templates per templateFile.
- Installs an
IndentAwareTemplateCache to make handlebars' own partial cache safe under prettyPrint(true).
No public API changes. No template changes. No swagger-codegen-generators change required.
Summary
HandlebarTemplateEngine.getRendered(...)creates a newHandlebarsinstance 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:
org.antlr.v4.runtime.atn.LexerATNSimulator.closure(...)— jknack's parser.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
ConcurrentMapTemplateCachecannot be used as-is for handlebars partials inprettyPrint(true)mode.Partial.mergewraps the partial'sTemplateSourcein an anonymous class whoseequals/hashCodedelegate only to the underlying source (filename + lastModified), but whosecontent()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:
Handlebarsinstance perHandlebarTemplateEngine(and so per generation), registering helpers once.Templates pertemplateFile.IndentAwareTemplateCacheto make handlebars' own partial cache safe underprettyPrint(true).No public API changes. No template changes. No
swagger-codegen-generatorschange required.