swagger-api / swagger-api/swagger-codegen

Slow generation: HandlebarTemplateEngine rebuilds Handlebars and reparses templates on every render

Open
#12,745 0 comments 0 reactions 0 assignees View on GitHub

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 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:

  1. Holds a single Handlebars instance per HandlebarTemplateEngine (and so per generation), registering helpers once.
  2. Caches compiled top-level Templates per templateFile.
  3. 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.