Skip to content

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

@wsandking

Description

@wsandking

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions