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
- Disambiguate the routes: give controllers distinct `@Controller('prefix')` paths or change methods so no two handlers claim the same method+path.
- If the duplicate is intentional (e.g., one route overriding another), relax the policy: `NestFactory.create(AppModule, { routeConflictPolicy: { duplicate: 'warn' } })`.
- When using `routeResolutionStrategy: 'specificity'`, verify shadow policies match your intent (`shadow: 'warn'`) or restructure paths so shadowing disappears.
- 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
- Give controllers unique path prefixes and never register the same controller in two modules.
- Run app.init() in CI so route conflicts block merges before deploy.
- Set routeConflictPolicy explicitly per conflict kind rather than relying on defaults across upgrades.
- Generate an OpenAPI snapshot in CI — new unexpected paths surface overlaps early.
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
- The middleware doesn't provide the 'use' method (${name})
- An invalid controller has been detected. "${className}" does
- Calling the "${methodName}" in the preview mode is not suppo
- HTTP adapter does not support filtering on host: "${host}"
- You must return an Observable stream to use Server-Sent Even
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/241e22af477e5c29.
Report an issue: GitHub.