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

  1. Search the codebase for the exact path string from the panic message — there will be two registrations; delete or rename one.
  2. If the duplication comes from generated code, dedupe the source list (map[string]bool) before iterating.
  3. If you intend multiple handlers, combine them into one handler chain or use router.Use(...) middleware instead of re-registering the path.
  4. 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

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


AI-assisted analysis of gin-gonic/gin@34dac209ff (2026-08-04). Data as JSON: /data/errors/ef48cda9d733b4d2.json. Report an issue: GitHub.