gin-gonic/gin · error
handlers are already registered for path '${fullPath}'
Error message
handlers are already registered for path '${fullPath}' What it means
Thrown by (*node).addRoute in tree.go:243 when addRoute reaches a leaf node that already has handlers attached. Gin's radix tree stores at most one handler chain per (method, path) pair, so registering the exact same method+path twice is treated as a programmer error rather than silently overwriting. The panic message echoes the duplicated fullPath so you can locate the offending registration.
Source
Thrown at tree.go:243
pathSeg := path
if n.nType != catchAll {
pathSeg, _, _ = strings.Cut(pathSeg, "/")
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
n.insertChild(path, fullPath, handlers)
return
}
// Otherwise add handle to current node
if n.handlers != nil {
panic("handlers are already registered for path '" + fullPath + "'")
}
n.handlers = handlers
n.fullPath = fullPath
return
}
}
// Search for a wildcard segment and check the name for invalid characters.
// Returns -1 as index, if no wildcard was found.
func findWildcard(path string) (wildcard string, i int, valid bool) {
// Find start
escapeColon := false
for start, c := range []byte(path) {
if escapeColon {
escapeColon = false
if c == ':' {
continue
}View on GitHub (pinned to 34dac209ff)
Solutions
- Search the codebase for the exact path string from the panic message — there will be two registrations; delete or rename one.
- If the duplication comes from generated code, dedupe the source list (map[string]bool) before iterating.
- If you intend multiple handlers, combine them into one handler chain or use router.Use(...) middleware instead of re-registering the path.
- Add a startup self-test that builds the router and asserts no panic; CI catches the regression before deploy.
Example fix
// before
router.GET("/healthz", healthHandler)
// ...elsewhere in the codebase...
router.GET("/healthz", healthHandler) // panics
// after — keep a single registration, share via middleware if needed
router.GET("/healthz", healthHandler) Defensive patterns
Strategy: validation
Validate before calling
// Detect duplicate (method, path) registrations before the engine is built.
func assertNoDuplicates(routes []struct{ Method, Path string }) error {
seen := make(map[string]string) // key "METHOD PATH" -> handler name
for _, r := range routes {
k := r.Method + " " + r.Path
if _, ok := seen[k]; ok {
return fmt.Errorf("duplicate route %s", k)
}
seen[k] = ""
}
return nil
} Try / catch
func safeHandle(e *gin.Engine, method, path string, h gin.HandlerFunc) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("duplicate route %s %s: %v", method, path, r)
}
}()
e.Handle(method, path, h)
return nil
} Prevention
- Declare all routes in a single slice or table driven by config so duplicates are visible and dedupable.
- Generate route tables from a single source of truth (OpenAPI spec, codegen) rather than hand-registering.
- Add a unit test that constructs the production Engine; duplicate routes panic during testing.
- Treat the panic message's fullPath as the literal string to grep for in the repo — there will be exactly two occurrences.
When it happens
Trigger: Calling router.GET("/healthz", h) twice on the same Engine; calling router.Handle("GET", "/items", h) after a previous router.GET("/items", ...) (same method+path); mounting a sub-router via router.Group("").GET("/", h) when the parent already mapped "/"; or auto-generated route registration loops that emit the same entry more than once.
Common situations: Two init() blocks or two packages both registering the same health-check endpoint; route lists generated from config where a duplicate slips in; refactoring that moves a handler into a group without removing the old top-level call; merging PRs that each add the same route.
Related errors
- '${pathSeg}' in new path '${fullPath}' conflicts with existi
- invalid escape string in path '${path}'
- only one wildcard per path segment is allowed, has: '${wildc
- wildcards must be named with a non-empty name in path '${ful
- catch-all routes are only allowed at the end of the path in
AI-assisted analysis of gin-gonic/gin@34dac209ff (2026-08-04).
Data as JSON: /data/errors/ef48cda9d733b4d2.json.
Report an issue: GitHub.