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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions