symfony/routing · error · LogicException

Cannot mix UTF-8 requirements with non-UTF-8 pattern

Error message

Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".

What it means

The reverse of error 72: the route's "utf8" option is set to true but the pattern itself is not valid UTF-8 (preg_match('//u', $pattern) fails). Mixing UTF-8 requirements with a non-UTF-8 pattern would produce inconsistent regexes, so compilePattern throws a LogicException.

Solutions

  1. Re-save the route definition file with UTF-8 encoding.
  2. Fix the pattern string so it is valid UTF-8 (check with mb_check_encoding($path, 'UTF-8')).
  3. Remove the utf8 => true option if the pattern is intentionally non-UTF-8 ASCII.
  4. Normalize input with mb_convert_encoding($path, 'UTF-8', 'ISO-8859-1') before creating the route.

Example fix

// before (pattern bytes are Latin-1)
$route = new Route("/caf\xE9/{name}", options: ['utf8' => true]);

// after (valid UTF-8)
$route = new Route('/café/{name}', options: ['utf8' => true]);
Defensive patterns

Strategy: validation

Validate before calling

if ($route->getOption('utf8') && !mb_check_encoding($route->getPath(), 'UTF-8')) {
    throw new \RuntimeException('Route path is not valid UTF-8 but the utf8 option is enabled.');
}

Try / catch

try {
    (new RouteCompiler())->compile($route);
} catch (\LogicException $e) {
    // fix encoding (mb_convert_encoding) or drop the utf8 option, then retry
}

Prevention

When it happens

Trigger: Compiling a route with options ['utf8' => true] whose path or host pattern is invalid or non-UTF-8 encoded bytes (e.g. a Latin-1 encoded config file containing '/café').

Common situations: Config files saved with the wrong encoding (ISO-8859-1 instead of UTF-8); routes built from external data with broken encoding; enabling utf8 globally but having legacy ASCII-escaped patterns.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of symfony/routing@83fa223250 (2026-09-14). Data as JSON: /api/errors/d12b6cc0023d64c4. Report an issue: GitHub.

Appendix: source

Thrown at RouteCompiler.php:121

            array_unique($variables)
        );
    }

    private static function compilePattern(Route $route, string $pattern, bool $isHost): array
    {
        $tokens = [];
        $variables = [];
        $matches = [];
        $pos = 0;
        $defaultSeparator = $isHost ? '.' : '/';
        $useUtf8 = preg_match('//u', $pattern);
        $needsUtf8 = $route->getOption('utf8');

        if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) {
            throw new \LogicException(\sprintf('Cannot use UTF-8 route patterns without setting the "utf8" option for route "%s".', $route->getPath()));
        }
        if (!$useUtf8 && $needsUtf8) {
            throw new \LogicException(\sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern));
        }

        // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
        // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
        preg_match_all('#\{(!)?([\w\x80-\xFF]+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
        foreach ($matches as $match) {
            $important = $match[1][1] >= 0;
            $varName = $match[2][0];
            // get all static text preceding the current variable
            $precedingText = substr($pattern, $pos, $match[0][1] - $pos);
            $pos = $match[0][1] + \strlen($match[0][0]);

            if (!\strlen($precedingText)) {
                $precedingChar = '';
            } elseif ($useUtf8) {
                preg_match('/.$/u', $precedingText, $precedingChar);
                $precedingChar = $precedingChar[0];
            } else {

View on GitHub (pinned to 83fa223250)