Skip to content

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

Description

@human-unidentified

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions