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
Nobody has claimed this yet.
- 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
- Create a Spring Cloud Gateway MVC application including
spring-cloud-function-contextor a similar classpath trigger. - Maintain default configuration values (do not explicitly set
spring.cloud.gateway.mvc.function.enabled). - Implement a local
@RestControllerendpoint mapped to a 2-segment path:@PostMapping("/path/action") public ResponseEntity<String> localAction() { return ResponseEntity.ok("Success"); } - Send a
POSTrequest to/path/action. - Observed Behavior: The request is captured by
RouterFunctionMapping(viafunctionroute), bypasses the local@RestController, and fails or gets routed contextually intoHandlerFunctions.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
- Change default fallback logic: Change
matchIfMissing = truetomatchIfMissing = false. Function routing integration should be an explicit opt-in feature rather than breaking standard Spring MVC route resolution patterns by default. - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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