Spring Cloud Gateway MVC — Migration from Reactive one
How to transfer from a reactive Spring Boot Cloud Gateway to the new non-reactive MVC Gateway easily and smoothly.
Spring Cloud Gateway (Reactive one) has long been a favored solution for incorporating an API gateway into a project. However, its foundation on the Reactive stack has often resulted in a codebase that can be difficult to write, interpret and maintain.
Recognizing these challenges, Spring unveiled an updated product at the close of 2023. This new offering is fully compatible with Spring Boot MVC, thereby marking a significant advancement in the field. It harnesses the power of both Spring Boot 3.2.x and JDK 21’s innovative Virtual Threads feature, known for its efficiency and lightweight thread management.
The fusion of Spring Boot 3.2.x and Spring Cloud Gateway MVC paves the way for the creation of highly scalable applications within the Servlet stack.
The drive behind
A key motivation to transition to the new product is to move away from the complexity of writing reactive code. For many developers, reactive coding can feel like uncharted territory, presenting its own challenges in both development and debugging. The new product, while it operates on a servlet stack, offers a well-known approach. This means that developers can introduce customizations or new code without the need for extensive Reactive programming knowledge. For instance, when integrating Spring Security into a project, the new product allows for a more straightforward implementation, bypassing the need for new Web Filters or implementing existing interfaces in Reactive code.
Migration from Spring Cloud Gateway Reactive
There are 4 aspects that need to be considered when coming to replace the Reactive gateway by the MVC one:
- Route
- Filters
- Error Handling
- Spring Security
We will now review each aspect and discuss what should be done in order to apply the transformation.
Route
- Basic Route
Let’s see how to convert the routing introduced in the application.yaml file below to Gateway MVC.
application.yaml in Reactive Gateway
There are two options for implementation:
- Option 1: use application.yaml
application.yaml in MVC Gateway
So, what can we learn from this Basic Route example?
1. Use context-path instead of base-path.
2. Predicates/Filters are supported.
3. The Path predicate doesn’t have a prefix of the context path of the server as we can see in the Reactive gateway.
4. We can’t use multiple paths separated by commas as we did in the Reactive gateway (known bug). We can either duplicate the routing block for each path or create a new custom predicate which can receive more than one path (See GatewayCustomRequestPredicates.java in References[1]).
5. The routes are defined under spring.cloud.gateway.mvc instead of under spring.cloud.gateway.
- Option 2: use Java Bean (Preferable option)
Spring boot Bean generation in MVC Gateway
- Http Client
Http client is another aspect of the gateway routing. The gateway uses it to forward the requests.
In Reactive Gateway it is implemented as follows:
In MVC Gateway it is implemented as follows:
We can see the following changes:
- proxy block is no longer supported.
- response-timeout is replaced by read-timeout.
- You can choose your http client by adding type: autodetect. The client library is detected according to the implementation jars in your classpath.
The lookup order is Apache, Jetty, OkHttp, SimpleClientHttpRequestFactory(spring boot implementation).
[The default value of type is jdk. Currently, there is an open bug on using it. We can’t use it with a Wiremock server as we get an error in the response.]
Filters
In Spring Cloud Gateway Reactive, we can use different types of filters. You can see below how to convert them to the equivalent type in Gateway MVC.
- WebFilter -> Filter
Generally, the gateway contains filters used to create correlation id, check authentication, check authorization and so on. To migrate a filter, we need to replace its interface to Filter instead of WebFilter.
- Default Filters
Default filters are not supported in Gateway MVC.
So we need to add this filter to each route or we can use the nest capability as described below.
The following example defines a nest routing for urls starting with /netstedjob1 or /nestedjob2. In each one we route sub paths to their target. For example, /nestedjob1/management/jobs is routed to http://localhost:7777.
Moreover, we can see before() & after() functions. These functions will be executed for all matched requests.
- Custom Filter
Custom filters/predicates can be used in route’s configuration in application.yaml.
However, it is not done automatically by creating the custom filter/predicate as in Spring Reactive.
In order to add a custom filter to be used in application.yaml routes, you need to:
1. Write the custom filter
2. Wrap the filter with HandlerFilterFunction + ‘@Shortcut’ on the method
3. Define FilterSupplier class which expose all methods
4. Add the FilterSupplier class to spring.factories under resources/META-INF
(See CustomBeforeFilterFunctions.java in References[1])
Error Handling
To control exceptions in our spring boot application, we generally create our own implementation which extends the relevant response handler.
In Spring boot MVC, we use @RestControllerAdvice. In Spring boot Reactive, we extend the class AbstractErrorWebExceptionHandler.
The annotation @RestControllerAdvice doesn’t handle exceptions which are thrown from routing flows. So, to control the exceptions in Gateway MVC, we need to define a function which will handle the exceptions.
Please note, there is no direct support of such an ‘exception’ function in Gateway MVC when using the route’s configuration. In order to implement it, we need to write a custom filter which catches exceptions and adds it to all routes in application.yaml.
Spring Security
Spring Security is the standard library for securing Spring Boot applications. Spring Security offers robust authentication and authorization, safeguards against common security vulnerabilities and supports an array of authentication mechanisms.
- Advanced filters [AuthenticationWebFilter -> AuthenticationFilter]
If you use AuthenticationWebFilter, you now need to use AuthenticationFilter instead. So, the authentication manager and all other handlers should be converted to the MVC ones. For example, instead of using ReactiveAuthenticationManager, you need to use AuthenticationManager. - Customization of oauth2
If your oauth2 implementation has customization by implementing classes such as ServerAuthorizationRequestRepository, ServerAuthenticationSuccessHandler, ServerAuthenticationFailureHandler, you should know that with MVC the classes are different and should be changed to AuthorizationRequestRepository, AuthenticationSuccessHandler, AuthenticationFailureHandler.
Migration issues
We migrated one of our applications successfully but faced some issues.
More issues are listed in the Limitations section below.
- We had a problem when using ‘forward’ operation. So, if you are using a ProxyExchange class in one of your routes, it is recommended to make a feasibility test for it. [See below issue #3340]
- We can’t add a header to response in “After Filter” when exception is thrown (since the response is immutable in this case).
Workaround: we can add an attribute to the request when an exception is thrown and check if it exists before trying to add the header.
Hands-On With Spring Cloud Gateway MVC
Here is a simple, yet complete example of an MVC Gateway.
spring-cloud-gateway-mvc-demo
Summary
As you have read, Spring Cloud Gateway MVC provides almost-equivalent functionality to the Reactive framework. And there are workarounds for the missing features (like default filters). From my point of view, a major part of the migration work is to convert the Spring Security code from Reactive code to Traditional code. Due to current limitations, I would recommend on creating the routes using Java RouterFunction Bean option rather than the configuration option. Yet, all in all, we can finally have our Gateways back in simpler, human-readable code, instead of the cumbersome Reactive code. And this is a very nice gift from Java 21.
Limitations & known issues in Gateway MVC
- When using the ‘forward‘ operation, there are unwanted side effects.
https://github.com/spring-cloud/spring-cloud-gateway/issues/3340 - Use the default http client with Wiremock server (workaround already described above)
https://github.com/spring-cloud/spring-cloud-gateway/issues/3141 - Add support for default-filters in server-mvc
https://github.com/spring-cloud/spring-cloud-gateway/issues/3177 - Allow registering custom filters and predicates in a programmatic way (not spring.factories)
https://github.com/spring-cloud/spring-cloud-gateway/issues/3250 - Documentation for gateway actuators is patchy.
https://github.com/spring-cloud/spring-cloud-gateway/issues/3246 - Look here for information how to add custom filter/predicate to spring.factories.
https://github.com/spring-cloud/spring-cloud-gateway/issues/3268
Resources & References
[1] Hands-on code:
[2] Spring documentation:
[3] Gateway MVC code examples: