spring-cloud / spring-cloud/spring-cloud-gateway

[Spring Cloud Gateway MVC]DefaultFunctionConfiguration automatically intercepts 1 and 2-segment local @RestController endpoints due to matchIfMissing=true

Open
#4,181 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

waiting-for-triage
Dominant language
Java
Stars
4.9k
Forks
3.5k
Avg merge
20h 57m
Merged PRs (30d)
8

Description

Bug Description

In Spring Cloud Gateway Server WebMVC, DefaultFunctionConfiguration is auto-configured with a default property match condition of matchIfMissing = true on spring.cloud.gateway.mvc.function.enabled.

Because Spring Framework 6.1+ changed the execution order of RouterFunctionMapping to -1 (via WebMvcConfigurationSupport), the un-prefixed, greedy path patterns /{path} and /{path}/{name} registered inside DefaultFunctionConfiguration take unconditional priority over standard user-defined @RestController paths.

As a result, any local controller with 1 or 2 segments (e.g., GET /items, POST /users/action) is entirely hijacked and handled by the gateway's functionroute router mapping loop, causing downstream target exceptions or context errors. Conversely, local endpoints with 3 or more segments (e.g., /api/v1/resource) successfully drop down to RequestMappingHandlerMapping because no 3+ segment pattern is declared in the default function routes.

This default-on behavior creates a highly destructive side effect for teams attempting to run hybrid microservices (local controller code executing inside the Gateway JVM).

Steps to Reproduce
  1. Create a Spring Cloud Gateway MVC application including spring-cloud-function-context or a similar classpath trigger.
  2. Maintain default configuration values (do not explicitly set spring.cloud.gateway.mvc.function.enabled).
  3. Implement a local @RestController endpoint mapped to a 2-segment path:
    @PostMapping("/path/action")
    public ResponseEntity<String> localAction() {
        return ResponseEntity.ok("Success");
    }
    
  4. Send a POST request to /path/action.
  5. Observed Behavior: The request is captured by RouterFunctionMapping (via functionroute), bypasses the local @RestController, and fails or gets routed contextually into HandlerFunctions.fn().
Root Cause Isolation

The issue stems directly from lines 32-41 of DefaultFunctionConfiguration.java:

@Configuration
@ConditionalOnClass(name = "org.springframework.cloud.function.context.FunctionCatalog")
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".function.enabled", havingValue = "true",
		matchIfMissing = true) // <-- ANTI-PATTERN DEFAULT
public class DefaultFunctionConfiguration {

	@Bean
	RouterFunction<ServerResponse> gatewayToFunctionRouter() {
		return route("functionroute")
				.POST("/{path}/{name}", fn("{path}/{name}")) // Overrides 2-segment controller paths
				.POST("/{path}", fn("{path}"))               // Overrides 1-segment controller paths
				.GET("/{path}/{name}", fn("{path}/{name}"))
				.GET("/{path}", fn("{path}"))
				.build();
	}
}

Because matchIfMissing = true is declared on global naked wildcards at a framework priority order of -1, it acts as a trap for any developer endpoint that is shorter than 3 segments deep.

Proposed Changes / Fixes
  1. Change default fallback logic: Change matchIfMissing = true to matchIfMissing = false. Function routing integration should be an explicit opt-in feature rather than breaking standard Spring MVC route resolution patterns by default.
  2. Namespace the routing paths: If the bean must persist by default, the paths should be namespaced (e.g., /functions/{path} and /functions/{path}/{name}) to protect root-level tenant namespace allocations on local applications.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading DefaultFunctionConfiguration.java, especially the @ConditionalOnProperty declaration and gatewayToFunctionRouter routes described at lines 32-41, then review how RouterFunctionMapping orders these routes against local controller mappings. Reproduce the one- and two-segment endpoint cases and verify that the completed change leaves local @RestController paths reachable while preserving intended function routing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring, spring-boot
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.