{"id":"62c52b76b8dbe274","repo":"gin-gonic/gin","slug":"catch-all-wildcard-path-in-new-path-fullpa","errorCode":null,"errorMessage":"catch-all wildcard '${path}' in new path '${fullPath}' conflicts with existing path segment '${pathSeg}' in existing prefix '${n.path}${pathSeg}'","messagePattern":"catch-all wildcard '(.+?)' in new path '(.+?)' conflicts with existing path segment '(.+?)' in existing prefix '(.+?)(.+?)'","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tree.go","lineNumber":353,"sourceCode":"\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Otherwise we're done. Insert the handle in the new leaf\n\t\t\tn.handlers = handlers\n\t\t\treturn\n\t\t}\n\n\t\t// catchAll\n\t\tif i+len(wildcard) != len(path) {\n\t\t\tpanic(\"catch-all routes are only allowed at the end of the path in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tif len(n.path) > 0 && n.path[len(n.path)-1] == '/' {\n\t\t\tpathSeg := \"\"\n\t\t\tif len(n.children) != 0 {\n\t\t\t\tpathSeg, _, _ = strings.Cut(n.children[0].path, \"/\")\n\t\t\t}\n\t\t\tpanic(\"catch-all wildcard '\" + path +\n\t\t\t\t\"' in new path '\" + fullPath +\n\t\t\t\t\"' conflicts with existing path segment '\" + pathSeg +\n\t\t\t\t\"' in existing prefix '\" + n.path + pathSeg +\n\t\t\t\t\"'\")\n\t\t}\n\n\t\t// currently fixed width 1 for '/'\n\t\ti--\n\t\tif i < 0 || path[i] != '/' {\n\t\t\tpanic(\"no / before catch-all in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tn.path = path[:i]\n\n\t\t// First node: catchAll node with empty path\n\t\tchild := &node{\n\t\t\twildChild: true,\n\t\t\tnType:     catchAll,","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/gin-gonic/gin/blob/34dac209ffb6ef85cc78c5d217bbb7ad001d68fd/tree.go#L335-L371","documentation":"Thrown by (*node).insertChild in tree.go:353 when a new catch-all route ('*') is being inserted under a node whose path already ends in '/' and which already has child segments. Because a catch-all would have to swallow everything after that slash, it cannot share the parent with pre-existing static or parametric children. The message names the conflicting child segment and the combined prefix so you can see what the catch-all would shadow.","triggerScenarios":"Registering router.GET(\"/static/*filepath\", h) AFTER router.GET(\"/static/css\", h), or mounting a catch-all under a group that already mapped specific children such as /api/*all after /api/users and /api/orders. The catch-all is rejected because it would conflict with the existing segment 'css' (or 'users').","commonSituations":"Adding a catch-all / fallback route to an existing API tree; introducing router.NoRoute-style handling via a real route that overlaps already-registered children; ordering routes such that specific paths come before the wildcard.","solutions":["Register the catch-all FIRST and the specific static children AFTER (Gin allows static children under a catch-all parent in many orderings, but the safe path is specific-before-general only when they do not share the immediate slash-parent — easiest is to test both orders).","Move the catch-all to a distinct prefix that has no existing children, e.g. /assets/*filepath kept separate from /static/css.","Use router.NoRoute(handler) or a middleware-based fallback instead of a catch-all route when you need a true 'match anything' behaviour without tree conflicts.","If you genuinely need both /static/css and /static/*filepath, restructure so the catch-all parent is its own node: register /static/*filepath alone and handle /static/css inside the handler by branching on the param."],"exampleFix":"// before\nrouter.GET(\"/static/css\", serveCSS)\nrouter.GET(\"/static/*filepath\", serveAll) // panics: conflicts with 'css'\n\n// after — distinct prefixes, no overlap\nrouter.GET(\"/static/css\", serveCSS)\nrouter.GET(\"/assets/*filepath\", serveAll)\n\n// or — single catch-all, branch inside the handler\nrouter.GET(\"/static/*filepath\", func(c *gin.Context) {\n    if c.Param(\"filepath\") == \"/css\" { serveCSS(c); return }\n    serveAll(c)\n})","handlingStrategy":"validation","validationCode":"// Static catch-all conflict predictor: a '/*x' route conflicts if a sibling\n// '/seg' is registered under the same parent. Maintain a registry.\ntype routeRegistry struct {\n    parentChildren map[string]map[string]bool // \"/static\" -> {\"css\": true}\n}\nfunc (r *routeRegistry) canAddCatchAll(parent, catchallParent string) error {\n    if kids, ok := r.parentChildren[catchallParent]; ok && len(kids) \u0001 0 {\n        return fmt.Errorf(\"catch-all under %s conflicts with existing children %v\", catchallParent, keys(kids))\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"func safeRegister(e *gin.Engine, method, path string, h gin.HandlerFunc) (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"catch-all conflict in %s: %v\", path, r)\n        }\n    }()\n    e.Handle(method, path, h)\n    return nil\n}","preventionTips":["Prefer router.NoRoute(...) or a fallback middleware over a catch-all route when the parent already has children.","Keep catch-alls on their own dedicated prefix (/assets/*filepath) separate from specific routes (/static/css).","When a catch-all must coexist with specific siblings, branch on c.Param inside a single handler instead of declaring multiple routes.","Register the catch-all first in your test builder to confirm whether the ordering resolves the conflict before relying on it in production."],"tags":["routing","catch-all","wildcard-conflict","gin","startup-panic"],"analyzedSha":"34dac209ffb6ef85cc78c5d217bbb7ad001d68fd","analyzedAt":"2026-08-04T21:26:18.438Z","schemaVersion":2}