Skip to content

fix(router): keep group auto-404 in sync with root RouteNotFound#3052

Merged
aldas merged 2 commits into
labstack:masterfrom
Solaris-star:fix/2485-group-404-fallback
Jul 23, 2026
Merged

fix(router): keep group auto-404 in sync with root RouteNotFound#3052
aldas merged 2 commits into
labstack:masterfrom
Solaris-star:fix/2485-group-404-fallback

Conversation

@Solaris-star

Copy link
Copy Markdown
Contributor

Summary

When a group has middleware, Echo auto-registers RouteNotFound routes with a nil handler so the group middleware still runs on 404s. Those handlers are filled from DefaultRouter.notFoundHandler.

e.RouteNotFound("/*", h) only installed h on the root tree node. It did not update r.notFoundHandler, so later group auto-404 routes kept the default JSON {"message":"Not Found"} instead of the root catch-all.

Fix

On DefaultRouter.Add, if method is RouteNotFound and path is /* or "", also set r.notFoundHandler = route.Handler.

Test plan

  • TestGroup_RouteNotFoundFallsBackToRoot (issue repro: /foo, /v0/foo, /v1/foo)
  • TestGroup_RouteNotFound* / TestGroup_UseMultipleTimes
  • go test .

Fixes #2485

Group.Use auto-registers nil-handler RouteNotFound routes so group
middleware still runs on 404s. Those handlers were filled from
r.notFoundHandler at registration time, which stayed the default even
after e.RouteNotFound("/*", h). Groups with middleware therefore used
the stock 404 JSON body instead of the root catch-all (labstack#2485).

When a RouteNotFound is added for /* or "", update r.notFoundHandler so
later group auto-404 routes inherit the same handler.

Fixes labstack#2485

@aldas aldas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think adding a route should have side-effects like that to Router.

you can achieve same (global 404 handler) when you create Router with your own NotFoundHandler

so this

	e := echo.New()
	e.RouteNotFound("/*", func(c *echo.Context) error {
		return c.NoContent(http.StatusNotFound)
	})

should be

	e := echo.NewWithConfig(echo.Config{
		Router: echo.NewRouter(echo.RouterConfig{
			NotFoundHandler: func(c *echo.Context) error {
				return c.NoContent(http.StatusNotFound)
			},
		}),
	})

…tFoundHandler

Per maintainer guidance, adding a route via e.RouteNotFound must not have
side effects on the Router. Revert the Add() mutation of r.notFoundHandler.

For groups that auto-register catch-all 404 routes (i.e. groups with
middleware), the supported way to customise the 404 handler is
RouterConfig.NotFoundHandler, which the group's nil-handler routes resolve
to at registration time and which group middleware wraps. Update the
existing labstack#2485 test expectations to reflect this design and add
TestGroup_RouteNotFoundUsesRouterConfig documenting the supported path.
@Solaris-star

Copy link
Copy Markdown
Contributor Author

@aldas agreed — a call to add a route shouldn't mutate the Router like that. I've dropped the side-effect entirely.

What the reworked PR does instead:

  • Reverts the r.notFoundHandler mutation in DefaultRouter.Add.
  • Documents that the supported way to customise the 404 for groups (which auto-register catch-all routes when they have middleware) is RouterConfig.NotFoundHandler, exactly as you pointed out — the group's nil-handler 404 routes resolve to it at registration time, and group middleware still wraps them.
  • Updates the two TestGroup_RouteNotFoundWithMiddleware cases that had encoded the old (side-effecting) expectation: they now assert the group auto-404 uses the configured NotFoundHandler (global), not a later e.RouteNotFound.
  • Adds TestGroup_RouteNotFoundUsesRouterConfig demonstrating the recommended pattern end-to-end.

So this PR now just documents + tests the existing intended behaviour rather than changing routing. If you think #2485 is better served purely by a docs note and no code/test changes at all, I can trim it further — let me know.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.34%. Comparing base (dcb05f0) to head (51adb32).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3052   +/-   ##
=======================================
  Coverage   93.34%   93.34%           
=======================================
  Files          43       43           
  Lines        4735     4735           
=======================================
  Hits         4420     4420           
  Misses        192      192           
  Partials      123      123           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aldas aldas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aldas
aldas merged commit 2e1ed48 into labstack:master Jul 23, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RouteNotFound handler does not falls back to root one

2 participants