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-context or a similar classpath trigger.
- Maintain default configuration values (do not explicitly set
spring.cloud.gateway.mvc.function.enabled).
- Implement a local
@RestController endpoint mapped to a 2-segment path:
@PostMapping("/path/action")
public ResponseEntity<String> localAction() {
return ResponseEntity.ok("Success");
}
- Send a
POST request to /path/action.
- 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
- 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.
- 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.
Bug Description
In Spring Cloud Gateway Server WebMVC,
DefaultFunctionConfigurationis auto-configured with a default property match condition ofmatchIfMissing = trueonspring.cloud.gateway.mvc.function.enabled.Because Spring Framework 6.1+ changed the execution order of
RouterFunctionMappingto-1(viaWebMvcConfigurationSupport), the un-prefixed, greedy path patterns/{path}and/{path}/{name}registered insideDefaultFunctionConfigurationtake unconditional priority over standard user-defined@RestControllerpaths.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'sfunctionrouterouter 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 toRequestMappingHandlerMappingbecause 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
spring-cloud-function-contextor a similar classpath trigger.spring.cloud.gateway.mvc.function.enabled).@RestControllerendpoint mapped to a 2-segment path:POSTrequest to/path/action.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:Because
matchIfMissing = trueis 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
matchIfMissing = truetomatchIfMissing = false. Function routing integration should be an explicit opt-in feature rather than breaking standard Spring MVC route resolution patterns by default./functions/{path}and/functions/{path}/{name}) to protect root-level tenant namespace allocations on local applications.