Reactive Programming for Java - 1.0

Extension ID

com.castsoftware.java.reactive 

What’s new?

See Reactive Programming for Java - 1.0 - Release Notes .

Description

This extension provides support for Java Reactive web operation calls made from Java code through packet org.springframework.web.reactive.function.server

The chosen approach for modelization is about web operation object on the server-side which is shown in more detail in the following table.

Supported API

Method Support Parameter(s) Notes

org.springframework.web.reactive.function.server.RouterFunctions.route

(tick)


Normally used to pair with RouterFunctions.Builder.X methods

(tick)

RequestPredicate  

HandlerFunction

Quick explanation:

route(GET("/posts"), A)
1 web operation object:
  • HTTP method: GET
  • URL: /posts
  • Handler: A

org.springframework.web.reactive.function.server.RouterFunctions.nest

(tick)

RequestPredicate 

RouterFunction

Quick explanation:

nest(RequestPredicates.path("/posts"), route(RequestPredicates.GET("/"), A)

1 web operation object:

  • HTTP method: GET
  • URL: /posts/
  • Handler: A

org.springframework.web.reactive.function.server.RouterFunctions.Builder.route

(tick)

RequestPredicate  

HandlerFunction

Quick explanation:

route().route(RequestPredicates.GET("/posts"), B).build()

1 web operation object:

  • HTTP method: GET
  • URL: /posts
  • Handler: B

org.springframework.web.reactive.function.server.RouterFunctions.Builder.add

(tick)

RouterFunction

Quick explanation:

route().path("/main", builder -> builder.add(route().GET("/posts", B).build())
                                        .add(route().GET("/home", B).build()))
                        .build();

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /main/posts
  • Handler: B

Second:

  • HTTP method: GET
  • URL: /main/home
  • Handler: B

org.springframework.web.reactive.function.server.RouterFunctions.Builder.nest

(tick)

RequestPredicate

Supplier>

Quick explanation:

route().nest(RequestPredicates.path("/main"), 
builder -> builder.add(route().GET("/posts", B).build())
                  .add(route().GET("/home", B).build()))
                        .build()

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /main/posts
  • Handler: B

Second:

  • HTTP method: GET
  • URL: /main/home
  • Handler: B
(tick)

RequestPredicate

Consumer

org.springframework.web.reactive.function.server.RouterFunctions.Builder.path

(tick)

RequestPredicate

Supplier>

Quick explanation:

route().path("/posts", builder -> builder.GET(B)).build()

1 web operation object:

  • HTTP method: GET
  • URL: /posts/{}
  • Handler: B
(tick)

RequestPredicate

Consumer

org.springframework.web.reactive.function.server.RouterFunctions.Builder.GET

(tick)

HandlerFunction

//

(tick)

String

HandlerFunction

Quick explanation:

route().path("/posts", () -> {
                                return RouterFunctions.route()
                                        .GET("/", B)
                                        .POST("/", C)
                                        .GET("/{id}", D)
                                        .build();
                            })
                    .build()

3 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts/
  • Handler: B

Second:

  • HTTP method: POST
  • URL: /posts/
  • Handler: C

Third:

  • HTTP method: GET
  • URL: /posts/{id}
  • Handler: D
(tick)

RequestPredicate

HandlerFunction

//

(tick)

String

RequestPredicate

HandlerFunction

Quick explanation:

route().GET("/posts", RequestPredicates.queryParam("c", "d"), B)
       .GET("/posts", RequestPredicates.queryParam("a", "b"), B)
                    .build()

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts?c=d
  • Handler: B

Second:

  • HTTP method: POST
  • URL: /posts?a=b
  • Handler: B

Normal url /posts is no longer valid based on real test.

org.springframework.web.reactive.function.server.RouterFunctions.Builder.POST

(tick)

HandlerFunction

//

(tick)

String

HandlerFunction

//

(tick)

RequestPredicate

HandlerFunction

//

(tick)

String

RequestPredicate

HandlerFunction

//

org.springframework.web.reactive.function.server.RouterFunctions.Builder.PUT

(tick)

HandlerFunction

//

(tick)

String

HandlerFunction

//

(tick)

RequestPredicate

HandlerFunction

//

(tick)

String

RequestPredicate

HandlerFunction

//

org.springframework.web.reactive.function.server.RouterFunctions.Builder.DELETE

(tick)

HandlerFunction

//

(tick)

String

HandlerFunction

//

(tick)

RequestPredicate

HandlerFunction

//

(tick)

String

RequestPredicate

HandlerFunction

//

org.springframework.web.reactive.function.server.RouterFunctions.Builder.HEAD

(tick)

HandlerFunction

//

(tick)

String

HandlerFunction

//

(tick)

RequestPredicate

HandlerFunction

//

(tick)

String

RequestPredicate

HandlerFunction

//

org.springframework.web.reactive.function.server.RouterFunctions.Builder.before

(tick) Function

Quick explanation:

RouterFunctions.route().path("/hello", 
                        b1 -> b1.nest(accept(MediaType.APPLICATION_JSON), 
                                 b2 -> b2.GET("/get", this::getHello)
                                         .GET(this::handleHello)                                                                                                     
                                         .before(FilterHandler1))
                                  .POST(this::sayHello))
                                  .after(FilterHandler2)
                        .build()

3 web operation objects:

First:

  • HTTP method: GET
  • URL: /hello/get
  • Handlers: 
    1. getHello
    2. FilterHandler1
    3. FilterHandler2

Second:

  • HTTP method: GET
  • URL: /hello/{}
  • Handlers:
    1. handleHello
    2. FilterHandler1
    3. FilterHandler2

Third:

  • HTTP method: POST
  • URL: /hello/{}
  • Handlers:
    1. sayHello
    2. FilterHandler2

org.springframework.web.reactive.function.server.RouterFunctions.Builder.after

(tick) BiFunction

org.springframework.web.reactive.function.server.RouterFunctions.Builder.filter

(tick) HandlerFilterFunction

Quick explanation:

RouterFunctions.route()
                .GET("/hello", this::handleHello)
                .filter(this::filterLogic)
                .build()

1 web operation object:

  • HTTP method: GET
  • URL: /hello
  • Handlers: 
    1. handleHello
    2. filterLogic

org.springframework.web.reactive.function.server.RouterFunction.andRoute

(tick)

RequestPredicate 

HandlerFunction

Quick explanation:

route(GET("/posts"), B)
       .andRoute(POST("/posts"), C)

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts
  • Handler: B

Second:

  • HTTP method: POST
  • URL: /posts
  • Handler: C

org.springframework.web.reactive.function.server.RouterFunction.andNest

(tick)

RequestPredicate 

RouterFunction

Quick explanation:

nest(path("/posts"),
                    nest(accept(APPLICATION_JSON), route(GET("/{id}"), D))
                    .andNest(accept(APPLICATION_JSON), route(POST("/"), C)))

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts/{id}
  • Handler: D

Second:

  • HTTP method: POST
  • URL: /posts/
  • Handler: C

org.springframework.web.reactive.function.server.RouterFunction.andOther

(tick)

RouterFunction

Quick explanation:

nest(path("/posts"),
                    nest(accept(APPLICATION_JSON), route(GET("/{id}"), D))
                    .andNest(accept(APPLICATION_JSON), route(POST("/"), C))
                ).andOther(route(GET("/"), B))

3 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts/{id}
  • Handler: D

Second:

  • HTTP method: POST
  • URL: /posts/
  • Handler: C

Third:

  • HTTP method: POST
  • URL: /{}
  • Handler: B

org.springframework.web.reactive.function.server.RouterFunction.and

(tick)

RouterFunction

Quick explanation:

route(GET("/posts"), B)
                .and(route(POST("/posts"), C))
                .and(route(GET("/posts/{id}"), D))

3 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts
  • Handler: B

Second:

  • HTTP method: POST
  • URL: /posts
  • Handler: C

Third:

  • HTTP method: GET
  • URL: /posts/{id}
  • Handler: D

org.springframework.web.reactive.function.server.RequestPredicates.GET

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.POST

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.PUT

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.DELETE

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.HEAD

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.all

(tick)


Quick explanation:

route(RequestPredicates.all(), A)

1 web operation object:

  • HTTP method: ANY
  • URL: /{}
  • Handler: A

org.springframework.web.reactive.function.server.RequestPredicates.path

(tick)

String

//

org.springframework.web.reactive.function.server.RequestPredicates.method

(tick)

HttpMethod

Quick explanation:

nest(path("/posts"), route(method(HttpMethod.GET), B))

1 web operation object:

  • HTTP method: GET
  • URL: /posts
  • Handler: B

org.springframework.web.reactive.function.server.RequestPredicates.methods

(tick)

HttpMethod...

Quick explanation:

nest(path("/posts"), route(RequestPredicates.method(HttpMethod.GET), B)
                   .andRoute(RequestPredicates.methods(HttpMethod.POST, HttpMethod.PUT), C))

3 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts/{}
  • Handler: B

Second:

  • HTTP method: POST
  • URL: /posts/{}
  • Handler: C

Third:

  • HTTP method: PUT
  • URL: /posts/{}
  • Handler: C

org.springframework.web.reactive.function.server.RequestPredicate.and

(tick)

RequestPredicate

Quick explanation:

route(path("/posts").and(method(GET)), B)

1 web operation object:

  • HTTP method: GET
  • URL: /posts
  • Handlers: B

org.springframework.web.reactive.function.server.RequestPredicate.or

(tick)

RequestPredicate

Quick explanation:

route(GET("/posts").or(GET("/content")), B)

2 web operation objects:

First:

  • HTTP method: GET
  • URL: /posts
  • Handler: B

Second:

  • HTTP method: GET
  • URL: /content
  • Handler: B

Supported handler binding

Lambda

Lambda to internal MethodCall
route(GET("/posts"), A->A.B())

Handler is passed as a lambda A->A.B() pointing to an internal MethodCall A.B() which creates a routing value of GET request and URL value of /posts

Both A (class) and B (method) are declared and defined by user.

Lambda to external MethodCall
route().path("/posts", builder -> builder.GET(ClassC::D)).build()

Handler is passed as MethodReference ClassC::D inside an external MethodCall builder.GET() which creates a routing value of GET request and URL value of /posts/{}

GET is an API method found in instance of builder of an dedicated API class

Both ClassC (class) and D (method) are declared and defined by user

MethodCall

route(GET("/posts"), E())

Handler is passed as a call of an internal method E() which creates a routing value of GET request and URL value of /posts

MethodReference

route(GET("/posts"), this::all)

Handler is passed as a reference to an internal method all which creates a routing value of GET request and URL value of /posts

Identifier

route(GET("/posts"), all)

Handler is passed as a variable -  an identifier all used to referencing a method which creates a routing value of GET request and URL value of /posts 

The referenced method is determined based following this order:

@Bean method specified by @Qualifier >> @Bean method annotated with @Primary >> @Bean method with matching declared Bean name >> @Bean method with matching normal name >> Unique @Bean method found (regardless of name).

@Bean method specified by @Qualifier
@Bean
public RouterFunction<ServerResponse> A (@Qualifier("C") HandlerFunction<ServerResponse> B) {
    return route(GET("/posts/{id}"), B);
}

 C is the name declared by the @Bean that annotates the method or the name of the method itself.

@Bean method annotated with @Primary
@Primary
@Bean()
public HandlerFunction<ServerResponse> D() {
    return E;
}
@Bean method with declared Bean name
@Bean("F")
public HandlerFunction<ServerResponse> G() {
    return G;
}

F is the Bean name of method with G as its normal name, in this case the name F takes precedence over the name G.

Unique @Bean method found
@Bean("B")
public HandlerFunction<ServerResponse> Y() {
    return Z;
}

@Bean
public RouterFunction<ServerResponse> newRoute(HandlerFunction<ServerResponse> X) {
    return route(GET("/posts/{id}"), X);
}

newRoute will automatically bind Y to its argument X, in other word X will be replaced with Y since Y is the only present @Bean method in the current configuration.

Multiple handlers

RouterFunctions.route()
                .GET("/hello", A)
                .filter(B)
                .build()

One web operation object is created with 2 binding handlers: A and B

Compatibility

AIP Core release Supported
8.3.x (tick)
com.castsoftware.jee.1.3.5-funcrel (tick)
com.castsoftware.internal.platform.0.9.15 (tick)
com.castsoftware.wbslinker.1.7.23-funcrel (tick)

Dependencies with other extensions

The Reactive Programming for Java extension requires that ≥ 1.3.5-funcrel of the JEE Analyzer is also installed and used in order to ensure the most complete set of results. This dependency with the JEE Analyzer is not automatically handled when installing the Reactive Programming for Java extension, therefore you must ensure that the JEE Analyzer  is already installed before starting an analysis.

Download and installation instructions

The extension will not be automatically installed by CAST Console, therefore you should ensure that the extension is manually installed using the Application - Extensions interface:

What results can you expect?

Objects

Icon Description

Spring WebFlux Get Operation

Spring WebFlux Put Operation

Spring WebFlux Post Operation

Spring WebFlux Delete Operation

Spring WebFlux Any Operation

Examples

Spring 5 examples

Axon lock application

From axon-lock\axon-app\src\main\java\daggerok\AxonApplication.java

 @Bean RouterFunction<ServerResponse> routes() {

      return

          route(POST("/api/v1/register-guest"), request -> {

            final String uuid = UUID.randomUUID().toString();

            final URI uri = request.uriBuilder()
                                   .path(uuid)
                                   .build();

            return created(uri).body(request.bodyToMono(Map.class)
                                            .map(map -> map.get("name"))
                                            .map(name -> {

                                              log.info("create user {}", name);
                                              return "";

                                            }).subscribeOn(Schedulers.elastic()), String.class);

          })

          ;
    }

One Java Reactive POST object:

POST("/api/v1/register-guest")

And its associated handler:

request -> {

            final String uuid = UUID.randomUUID().toString();

            final URI uri = request.uriBuilder()
                                   .path(uuid)
                                   .build();

            return created(uri).body(request.bodyToMono(Map.class)
                                            .map(map -> map.get("name"))
                                            .map(name -> {

                                              log.info("create user {}", name);
                                              return "";

                                            }).subscribeOn(Schedulers.elastic()), String.class);

          }

Result:

Spring data count query

From spring-data-jpa-count-query-fix\src\main\java\daggerok\App.java

@Configuration
@RequiredArgsConstructor
class WebfluxRoutesConfig {

  private final OrderRepository orderRepository;

  @Bean
  HandlerFunction<ServerResponse> getCountQueryHandler() {
    return request ->
        ok().contentType(APPLICATION_JSON_UTF8)
            .body(Flux.fromIterable(orderRepository.findAllPrices()), Price.class);
  }

  @Bean
  HandlerFunction<ServerResponse> getOrdersHandler() {
    return request ->
        ok().contentType(APPLICATION_JSON_UTF8)
            .body(Flux.fromIterable(orderRepository.findAll()), Order.class);
  }

  @Bean
  HandlerFunction<ServerResponse> fallbackHandler() {
    return request -> {
      final URL url = Try.of(() -> request.uri().toURL())
                         .getOrElseThrow(() -> new RuntimeException("=/"));
      final String protocol = url.getProtocol();
      final int defaultPort = "https".equals(protocol) ? 443 : 80;
      final int currentPort = url.getPort();
      final int port = currentPort == -1 ? defaultPort : currentPort;
      final String baseUrl = format("%s://%s:%d", protocol, url.getHost(), port);
      return ok().body(Flux.just(
          format("GET orders -> %s/api/orders/", baseUrl),
          format("GET pages -> %s/api/", baseUrl),
          format("GET  -> %s/", baseUrl)
      ), String.class);
    };
  }

  @Bean
  RouterFunction routes(final HandlerFunction<ServerResponse> fallbackHandler) {
    return
        nest(
            path("/"),
            nest(
                accept(APPLICATION_JSON),
                route(
                    GET("/api/orders"),
                    getOrdersHandler()
                )
            ).andNest(
                accept(APPLICATION_JSON),
                route(
                    GET("/api"),
                    getCountQueryHandler()
                )
            )
        ).andOther(
            route(
                GET("/home"),
                fallbackHandler
            )
        )
        ;
  }
}

Three Java Reactive objects:

First GET object:

path("/") ... GET("/api/orders")

And its corresponding handler:

request ->
        ok().contentType(APPLICATION_JSON_UTF8)
            .body(Flux.fromIterable(orderRepository.findAll()), Order.class)

Result:

Second GET object:

path("/") ... GET("/api")

And its corresponding handler:

request ->
        ok().contentType(APPLICATION_JSON_UTF8)
            .body(Flux.fromIterable(orderRepository.findAll()), Order.class)

Result:

Third GET object:

GET("/home")

And its corresponding handler:

fallbackHandler

Result:

About Project Reactor

Although it is primarily a library for reactive programming and focusing on handling asynchronous data streams using reactive types like Mono and Flux, it does not inherently provide routing or HTTP method creation functionalities. 

It can be used within the context of an HTTP server framework to handle incoming requests reactively and execute corresponding logic based on the requested URL paths and HTTP methods.

Limitation

Not enough use of functional programming pattern in client applications to validate the current support performance.