Using Reactive Routes

    The code presented in this guide is available in this Github repository under the

    Before going further, let’s have a look at the HTTP layer of Quarkus. Quarkus HTTP support is based on a non-blocking and reactive engine (Eclipse Vert.x and Netty). All the HTTP requests your application receive are handled by event loops (IO Thread) and then are routed towards the code that manages the request. Depending on the destination, it can invoke the code managing the request on a worker thread (Servlet, Jax-RS) or use the IO Thread (reactive route). Note that because of this, a reactive route must be non-blocking or explicitly declare its blocking nature (which would result by being called on a worker thread).

    Declaring reactive routes

    The first way to use reactive routes is to use the @Route annotation. To have access to this annotation, you need to add the quarkus-vertx-web extension:

    In your pom.xml file, add:

    Then in a bean, you can use the @Route annotation as follows:

    1. package org.acme.reactive.routes;
    2. import io.quarkus.vertx.web.Route;
    3. import io.quarkus.vertx.web.RoutingExchange;
    4. import io.vertx.core.http.HttpMethod;
    5. import io.vertx.ext.web.RoutingContext;
    6. import javax.enterprise.context.ApplicationScoped;
    7. @ApplicationScoped (1)
    8. public class MyDeclarativeRoutes {
    9. // neither path nor regex is set - match a path derived from the method name
    10. @Route(methods = HttpMethod.GET) (2)
    11. void hello(RoutingContext rc) { (3)
    12. rc.response().end("hello");
    13. }
    14. @Route(path = "/world")
    15. String helloWorld() { (4)
    16. return "Hello world!";
    17. }
    18. @Route(path = "/greetings", methods = HttpMethod.GET)
    19. void greetings(RoutingExchange ex) { (5)
    20. ex.ok("hello " + ex.getParam("name").orElse("world"));
    21. }
    22. }

    More details about using the RoutingContext is available in the .

    The @Route annotation allows you to configure:

    • The path - for routing by path, using the Vert.x Web format

    • The regex - for routing with regular expressions, see

    • The methods - the HTTP verb triggering the route such as GET, POST…​

    • The type - it can be normal (non-blocking), blocking (method dispatched on a worker thread), or failure to indicate that this route is called on failures

    • The order - the order of the route when several routes are involved in handling the incoming request. Must be positive for regular user routes.

    • The produced and consumed mime types using produces, and consumes

    For instance, you can declare a blocking route as follows:

    1. @Route(methods = HttpMethod.POST, path = "/post", type = Route.HandlerType.BLOCKING)
    2. public void blocking(RoutingContext rc) {
    3. // ...
    4. }

    The @Route annotation is repeatable and so you can declare several routes for a single method:

    1. @Route(path = "/first") (1)
    2. @Route(path = "/second")
    3. public void route(RoutingContext rc) {
    4. // ...
    5. }
    1Each route can use different paths, methods…​

    If no content-type header is set then we will try to use the most acceptable content type as defined by io.vertx.ext.web.RoutingContext.getAcceptableContentType().

    1. @Route(path = "/person", produces = "text/html") (1)
    2. String person() {
    3. // ...
    4. }
    1If the accept header matches text/html we set the content type automatically.

    You may end up with multiple routes matching a given path. In the following example, both route matches /accounts/me:

    1. @Route(path = "/accounts/:id", methods = HttpMethod.GET)
    2. void getAccount(RoutingContext ctx) {
    3. ...
    4. }
    5. @Route(path = "/accounts/me", methods = HttpMethod.GET)
    6. void getCurrentUserAccount(RoutingContext ctx) {
    7. ...
    8. }

    As a consequence, the result is not the expected one as the first route is called with the path parameter id set to me. To avoid the conflict, use the order attribute:

    1. @Route(path = "/accounts/:id", methods = HttpMethod.GET, order = 2)
    2. void getAccount(RoutingContext ctx) {
    3. ...
    4. }
    5. @Route(path = "/accounts/me", methods = HttpMethod.GET, order = 1)
    6. void getCurrentUserAccount(RoutingContext ctx) {
    7. ...
    8. }

    By giving a lower order to the second route, it gets evaluated first. If the request path matches, it is invoked, otherwise the other routes are evaluated.

    @RouteBase

    This annotation can be used to configure some defaults for reactive routes declared on a class.

    1. @RouteBase(path = "simple", produces = "text/plain") (1) (2)
    2. public class SimpleRoutes {
    3. @Route(path = "ping") // the final path is /simple/ping
    4. void ping(RoutingContext rc) {
    5. rc.response().end("pong");
    6. }
    7. }

    A route method must be a non-private non-static method of a CDI bean. If the annotated method returns void then it has to accept at least one argument - see the supported types below. If the annotated method does not return void then the arguments are optional.

    A route method can accept arguments of the following types:

    • io.vertx.ext.web.RoutingContext

    • io.vertx.reactivex.ext.web.RoutingContext

    • io.quarkus.vertx.web.RoutingExchange

    • io.vertx.core.http.HttpServerRequest

    • io.vertx.reactivex.core.http.HttpServerRequest

    • io.vertx.reactivex.core.http.HttpServerResponse

    Furthermore, it is possible to inject the HttpServerRequest parameters into a method parameter annotated with @io.quarkus.vertx.web.Param:

    Parameter TypeObtained via

    java.lang.String

    routingContext.request().getParam()

    routingContext.request().getParam()

    java.util.List<String>

    routingContext.request().params().getAll()

    Request Parameter Example

    1. String hello(@Param Optional<String> name) {
    2. return "Hello " + name.orElse("world");
    3. }

    The HttpServerRequest headers can be injected into a method parameter annotated with @io.quarkus.vertx.web.Header:

    Parameter TypeObtained via

    java.lang.String

    routingContext.request().getHeader()

    java.util.Optional<String>

    routingContext.request().getHeader()

    java.util.List<String>

    routingContext.request().headers().getAll()

    Request Header Example

    1. @Route
    2. String helloFromHeader(@Header("My-Header") String header) {
    3. return header;
    4. }

    The request body can be injected into a method parameter annotated with @io.quarkus.vertx.web.Body.

    Request Body Example

    Returning Unis

    In a reactive route, you can return a Uni directly:

    1. @Route(path = "/hello")
    2. Uni<String> hello(RoutingContext context) {
    3. return Uni.createFrom().item("Hello world!");
    4. }
    5. @Route(path = "/person")
    6. Uni<Person> getPerson(RoutingContext context) {
    7. return Uni.createFrom().item(() -> new Person("neo", 12345));
    8. }

    Returning Unis is convenient when using a reactive client:

    1. @Route(path = "/mail")
    2. Uni<Void> sendEmail(RoutingContext context) {
    3. return mailer.send(...);
    4. }

    The item produced by the returned Uni can be:

    • a string - written into the HTTP response directly

    • a buffer - written into the HTTP response directly

    • an object - written into the HTTP response after having been encoded into JSON. The content-type header is set to application/json if not already set.

    If the returned Uni produces a failure (or is null), an HTTP 500 response is written.

    Returning a Uni<Void> produces a 204 response (no content).

    You can also return a result directly:

    1. @Route(path = "/hello")
    2. String helloSync(RoutingContext context) {
    3. return "Hello world";
    4. }

    The method can return:

    • a string - written into the HTTP response directly

    • a buffer - written into the HTTP response directly

    • an object - written into the HTTP response after having been encoded into JSON. The content-type header is set to application/json if not already set.

    Returning Multis

    A reactive route can return a Multi. The items are written one by one, in the response. The response Transfer-Encoding header is set to chunked.

    1. @Route(path = "/hello")
    2. Multi<String> hellos(RoutingContext context) {
    3. return Multi.createFrom().items("hello", "world", "!"); (1)
    4. }
    1. Produces helloworld!

    The method can return:

    • a Multi<String> - the items are written one by one (one per chunk) in the response.

    • a Multi<Buffer> - the buffers are written one by one (one per chunk) without any processing.

    • a Multi<Object> - the items are encoded to JSON written one by one in the response.

    1. @Route(path = "/people")
    2. Multi<Person> people(RoutingContext context) {
    3. return Multi.createFrom().items(
    4. new Person("superman", 1),
    5. new Person("batman", 2),
    6. new Person("spiderman", 3));
    7. }

    The previous snippet produces:

    1. {"name":"superman", "id": 1} // chunk 1
    2. {"name":"batman", "id": 2} // chunk 2
    3. {"name":"spiderman", "id": 3} // chunk 3

    Streaming JSON Array items

    You can return a Multi to produce a JSON Array, where every item is an item from this array. The response is written item by item to the client. The content-type is set to application/json if not set already.

    To use this feature, you need to wrap the returned Multi using io.quarkus.vertx.web.ReactiveRoutes.asJsonArray:

    1. @Route(path = "/people")
    2. Multi<Person> people(RoutingContext context) {
    3. return ReactiveRoutes.asJsonArray(Multi.createFrom().items(
    4. new Person("superman", 1),
    5. new Person("batman", 2),
    6. new Person("spiderman", 3)));
    7. }

    The previous snippet produces:

    1. [
    2. {"name":"superman", "id": 1} // chunk 1
    3. ,{"name":"batman", "id": 2} // chunk 2
    4. ,{"name":"spiderman", "id": 3} // chunk 3
    5. ]

    Only Multi<String>, Multi<Object> and Multi<Void> can be written into the JSON Array. Using a Multi<Void> produces an empty array. You cannot use Multi<Buffer>. If you need to use Buffer, transform the content into a JSON or String representation first.

    You can return a Multi to produce an event source (stream of server sent events). To enable this feature, you need to wrap the returned Multi using io.quarkus.vertx.web.ReactiveRoutes.asEventStream:

    1. @Route(path = "/people")
    2. Multi<Person> people(RoutingContext context) {
    3. return ReactiveRoutes.asEventStream(Multi.createFrom().items(
    4. new Person("superman", 1),
    5. new Person("batman", 2),
    6. new Person("spiderman", 3)));
    7. }

    This method would produce:

    You can also implement the io.quarkus.vertx.web.ReactiveRoutes.ServerSentEvent interface to customize the event and id section of the server sent event:

    1. class PersonEvent implements ReactiveRoutes.ServerSentEvent<Person> {
    2. public String name;
    3. public int id;
    4. public PersonEvent(String name, int id) {
    5. this.name = name;
    6. this.id = id;
    7. }
    8. @Override
    9. public Person data() {
    10. }
    11. @Override
    12. public long id() {
    13. return id;
    14. }
    15. @Override
    16. public String event() {
    17. return "person";
    18. }

    Using a Multi<PersonEvent> (wrapped using io.quarkus.vertx.web.ReactiveRoutes.asEventStream) would produce:

    1. event: person
    2. data: {"name":"superman", "id": 1}
    3. id: 1
    4. event: person
    5. data: {"name":"batman", "id": 2}
    6. id: 2
    7. event: person
    8. data: {"name":"spiderman", "id": 3}
    9. id: 3

    Using the Vert.x Web Router

    You can also register your route directly on the HTTP routing layer by registering routes directly on the Router object. To retrieve the Router instance at startup:

    1. public void init(@Observes Router router) {
    2. router.get("/my-route").handler(rc -> rc.response().end("Hello from my route"));
    3. }

    Check the to know more about the route registration, options, and available handlers.

    Router access is provided by the quarkus-vertx-http extension. If you use quarkus-resteasy or quarkus-vertx-web, the extension will be added automatically.

    You can also register filters that would intercept incoming HTTP requests. Note that these filters are also applied for servlets, JAX-RS resources, and reactive routes.

    For example, the following code snippet registers a filter adding an HTTP header:

    1. package org.acme.reactive.routes;
    2. import io.vertx.ext.web.RoutingContext;
    3. public class MyFilters {
    4. @RouteFilter(100) (1)
    5. void myFilter(RoutingContext rc) {
    6. rc.response().putHeader("X-Header", "intercepting the request");
    7. rc.next(); (2)
    8. }
    9. }
    1The RouteFilter#value() defines the priority used to sort the filters - filters with higher priority are called first.
    2The filter is likely required to call the next() method to continue the chain.

    Adding OpenAPI and Swagger UI

    You can add support for and Swagger UI by using the quarkus-smallrye-openapi extension.

    Add the extension by running this command:

    1. ./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-openapi"

    This will add the following to your pom.xml:

    1. <dependency>
    2. <groupId>io.quarkus</groupId>
    3. <artifactId>quarkus-smallrye-openapi</artifactId>
    4. </dependency>

    This is enough to generate a basic OpenAPI schema document from your Vert.x Routes:

    1. curl http://localhost:8080/openapi

    You will see the generated OpenAPI schema document:

    1. ---
    2. openapi: 3.0.3
    3. info:
    4. title: Generated API
    5. version: "1.0"
    6. paths:
    7. /greetings:
    8. get:
    9. responses:
    10. "204":
    11. description: No Content
    12. /hello:
    13. get:
    14. responses:
    15. "204":
    16. description: No Content
    17. /world:
    18. get:
    19. responses:
    20. "200":
    21. description: OK
    22. content:
    23. '*/*':
    24. schema:
    25. type: string

    Also see .

    Adding MicroProfile OpenAPI Annotations

    You can use to better document your schema, example, adding header info, or specifying the return type on void methods might be usefull :

    1. @OpenAPIDefinition((1)
    2. info = @Info(
    3. title="Greeting API",
    4. version = "1.0.1",
    5. contact = @Contact(
    6. name = "Greeting API Support",
    7. url = "http://exampleurl.com/contact",
    8. email = "techsupport@example.com"),
    9. license = @License(
    10. name = "Apache 2.0",
    11. url = "http://www.apache.org/licenses/LICENSE-2.0.html"))
    12. )
    13. @ApplicationScoped
    14. public class MyDeclarativeRoutes {
    15. // neither path nor regex is set - match a path derived from the method name
    16. @Route(methods = HttpMethod.GET)
    17. @APIResponse(responseCode="200",
    18. description="Say hello",
    19. content=@Content(mediaType="application/json", schema=@Schema(type=SchemaType.STRING)))(2)
    20. void hello(RoutingContext rc) {
    21. rc.response().end("hello");
    22. }
    23. @Route(path = "/world")
    24. String helloWorld() {
    25. return "Hello world!";
    26. }
    27. @Route(path = "/greetings", methods = HttpMethod.GET)
    28. @APIResponse(responseCode="200",
    29. description="Greeting",
    30. content=@Content(mediaType="application/json", schema=@Schema(type=SchemaType.STRING)))
    31. void greetings(RoutingExchange ex) {
    32. ex.ok("hello " + ex.getParam("name").orElse("world"));
    33. }

    This will generate this OpenAPI schema:

    Using Swagger UI

    Swagger UI is included by default when running in dev or test mode, and can optionally added to prod mode. See Guide for more details.

    Navigate to localhost:8080/swagger-ui/ and you will see the Swagger UI screen:

    This guide has introduced how you can use reactive routes to define an HTTP endpoint. It also describes the structure of the Quarkus HTTP layer and how to write filters.