Skip to content

Scanner treats backslash as escape character outside quoted strings, diverging from Jinja2 behaviour #1304

@jmoraleda

Description

@jmoraleda

In Jinja2, the template scanner is not responsible for backslash interpretation. It locates block/variable/comment delimiters, extracts the raw content between them, and passes it to Python's expression parser. Backslash handling inside string literals is Python's concern, not the scanner's. A bare backslash outside a string literal is simply invalid Python, so Jinja2 never needs to handle it at the scanner level.

Jinjava's TokenScanner currently handles backslash at the scanner level unconditionally:

if (c == '\\') {
    ++currPost; // skips the next character regardless of quote context
    continue;
}

This causes two problems:

  1. Inside quoted strings: it works by accident for simple cases, but the scanner pre-consumes the escaped character before JUEL sees it, meaning the expression language never receives the original token intact.
  2. Outside quoted strings: a \ that is legitimately part of a Java expression (e.g. in a regex or a custom EL function) is silently swallowed by the scanner before the expression parser can interpret it.

The correct behaviour, matching Jinja2, is to treat \ as significant only inside quoted string literals — so that "\"" closes the string at the right place — and leave all other backslashes untouched for the expression parser. This affects both TokenScanner's char-based path and the new string-based path added in #1303. A fix would be a one-line change in both: move the backslash check inside the inQuote branch and remove it from the outer block-scanning logic

Behaviour change concern

This is a breaking change for any template that currently relies on \}} or \%} to prevent a closing delimiter from being recognized. We are not aware of Jinja2 supporting this pattern — in Python, embedding a literal }} inside a block expression would be a syntax error regardless — but Jinjava users may have come to depend on it as an undocumented escape mechanism.

We would like to ask the maintainers: is this use case known and intentional? If so, would the fix require a compatibility flag, or is a clean break acceptable given that the current behaviour diverges from Jinja2? We are happy to submit a PR once the preferred approach is clear.

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