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.

Eyal Sofer
AT&T Israel Tech Blog
6 min readJun 19, 2024

--

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

Resources & References

[1] Hands-on code:

[2] Spring documentation:

[3] Gateway MVC code examples:

spring-cloud-gateway-mvc-sample/pom.xml at main · spencergibb/spring-cloud-gateway-mvc-sample · GitHub

--

--