nestjs/nest · critical · RouteConflictException

Conflicting HTTP routes detected: - ${messages} Adjust rou

Error message

Conflicting HTTP routes detected:
  - ${messages}
Adjust route declarations or relax the 'routeConflictPolicy' option passed to NestFactory.create() to allow the application to start.

What it means

At bootstrap the route conflict detector classifies every pair of registered routes (duplicates, version conflicts, shadowing under the 'specificity' strategy) and applies the `routeConflictPolicy` option per conflict kind ('error' | 'warn' | 'off'; duplicates default to 'error'). When at least one conflict resolves to 'error', RouteConflictException is thrown listing every conflicting route so the app refuses to start with ambiguous routing.

Source

Thrown at packages/core/router/route-conflict-detector.ts:201

    if (conflicts.length === 0 || policy === undefined) return;

    const errorMessages: string[] = [];

    conflicts.forEach(conflict => {
      const policyForKind = policy[conflict.kind] ?? 'off';
      if (policyForKind === 'off') return;

      const message = RouteConflictDetector.describeConflict(conflict);

      if (policyForKind === 'warn') {
        logger.warn(message);
        return;
      }
      errorMessages.push(message);
    });

    if (errorMessages.length > 0) {
      throw new RouteConflictException(errorMessages);
    }
  }

  /**
   * Removes shadow conflicts that specificity sorting has already resolved.
   *
   * When `routeResolutionStrategy: 'specificity'` is active, the sort
   * promotes more-specific routes ahead of less-specific ones. A shadow
   * where the sort promoted the winner (it was declared *later* but sorted
   * *first*) is handled correctly at runtime — the more-specific route is
   * registered first and handles its requests while the less-specific route
   * handles the rest. Retaining such a conflict would cause `shadow: 'error'`
   * to abort an application whose routes actually work as intended.
   *
   * Shadows where the winner was already first in declaration order (the
   * sort did not swap them) are genuine and are kept unchanged. Duplicate
   * conflicts are always kept.
   *

View on GitHub (pinned to dd75d7bd8c)

Solutions

  1. Disambiguate the routes: give controllers distinct `@Controller('prefix')` paths or change methods so no two handlers claim the same method+path.
  2. If the duplicate is intentional (e.g., one route overriding another), relax the policy: `NestFactory.create(AppModule, { routeConflictPolicy: { duplicate: 'warn' } })`.
  3. When using `routeResolutionStrategy: 'specificity'`, verify shadow policies match your intent (`shadow: 'warn'`) or restructure paths so shadowing disappears.
  4. Run `app.init()` in CI to catch conflicts before deploy even if you relax runtime behavior.

Example fix

// before
@Controller('users') export class AdminUsersController { @Get() findAll() {} }
@Controller('users') export class PublicUsersController { @Get() findAll() {} } // duplicate -> error

// after
@Controller('admin/users') export class AdminUsersController { @Get() findAll() {} }
@Controller('users')   export class PublicUsersController { @Get() findAll() {} }

// or, if overlap is intended:
const app = await NestFactory.create(AppModule, {
  routeConflictPolicy: { duplicate: 'warn' },
});
Defensive patterns

Strategy: try-catch

Try / catch

// CI-friendly: surface every conflict route pair, then fail
try {
  const app = await NestFactory.create(AppModule);
  await app.init(); // runs conflict detection
} catch (e: any) {
  if (e?.name === 'RouteConflictException' || /Conflicting HTTP routes/i.test(e?.message)) {
    console.error(e.message); // lists each conflicting route
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: Two controllers (or repeated registrations of the same controller in modules) map the same method+path, e.g. `@Get('users')` under both a global-prefix-less and prefixed controller; a versioned route (URI versioning `v1/users`) colliding with a static segment; shadow conflicts where a less specific route would never match because a more specific one is declared first — with shadow policy set to 'error'; platform version upgrades where path-to-regexp pattern semantics changed and previously distinct routes now overlap.

Common situations: Adding a new controller whose paths unintentionally duplicate an existing one; registering the same controller in two modules; enabling URI versioning on legacy routes; adopting NestJS 11 where duplicate detection became strict; micro-frontends merging routers.

Related errors


AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21). Data as JSON: /api/errors/241e22af477e5c29. Report an issue: GitHub.