1. Introduction

RxJava is one of the most popular reactive programming libraries out there.

And Ratpack is a collection of Java libraries for creating lean and powerful web applications built on Netty.

In this tutorial, we'll discuss the incorporation of RxJava in a Ratpack application to create a nice reactive web app.

2. Maven Dependencies

Now, we first need the ratpack-core and ratpack-rx dependencies:


Note, by the way, that ratpack-rx imports the rxjava dependency for us.

3. Initial Setup

RxJava supports the integration of 3rd party libraries, using its plugin system. So, we can incorporate different execution strategies into RxJava's execution model. 

Ratpack plugs into this execution model via RxRatpack, which we initialize at startup:


Now, it's important to note that the method needs to be called only once per JVM run.

The result is that we'll be able to map RxJava's Observables into RxRatpack's Promise types and vice versa.

4. Observables to Promises

We can convert an Observable in RxJava into a Ratpack Promise.

However, there is a bit of a mismatch. See, a Promise emits a single value, but an Observable can emit a stream of them.

RxRatpack handles this by offering two different methods: promiseSingle() and promise().

So, let's assume we have a service named MovieService that emits a single promise on getMovie(). We'd use promiseSingle() since we know it will only emit once:

Handler movieHandler = (ctx) -> {
    MovieService movieSvc = ctx.get(MovieService.class);
    Observable<Movie> movieObs = movieSvc.getMovie();
      .then(movie -> ctx.render(Jackson.json(movie)));

On the other hand, if getMovies() can return a stream of movie results, we'd use promise():

Handler moviesHandler = (ctx) -> {
    MovieService movieSvc = ctx.get(MovieService.class);
    Observable<Movie> movieObs = movieSvc.getMovies();
      .then(movie -> ctx.render(Jackson.json(movie)));

Then, we can add these handlers to our Ratpack server like normal:

RatpackServer.start(def -> def.registryOf(rSpec -> rSpec.add(MovieService.class, new MovieServiceImpl()))
  .handlers(chain -> chain
    .get("movie", movieHandler)
    .get("movies", moviesHandler)));

5. Promises to Observables

Conversely, we can map a Promise type in Ratpack back to an RxJava Observable. 

RxRatpack again has two methods: observe() and observeEach().

In this case, we'll imagine we have a movie service that returns Promises instead of Observables.

With our getMovie(), we'd use observe():

Handler moviePromiseHandler = ctx -> {
    MoviePromiseService promiseSvc = ctx.get(MoviePromiseService.class);
    Promise<Movie> moviePromise = promiseSvc.getMovie();
      .subscribe(movie -> ctx.render(Jackson.json(movie)));

And when we get back a list, like with getMovies(), we'd use observeEach():

Handler moviesPromiseHandler = ctx -> {
    MoviePromiseService promiseSvc = ctx.get(MoviePromiseService.class);
    Promise<List<Movie>> moviePromises = promiseSvc.getMovies();
        .subscribe(movie -> ctx.render(Jackson.json(movie)));

Then, again, we can add the handlers as expected:

RatpackServer.start(def -> def.registryOf(regSpec -> regSpec
  .add(MoviePromiseService.class, new MoviePromiseServiceImpl()))
    .handlers(chain -> chain
      .get("movie", moviePromiseHandler)
      .get("movies", moviesPromiseHandler)));

6. Parallel Processing

RxRatpack supports parallelism with the help of the fork() and forkEach() methods.

And it follows a pattern we've already seen with each.

fork() takes a single Observable and parallelizes its execution onto a different compute thread. Then, it automatically binds the data back to the original execution.

On the other hand, forkEach() does the same for each element emitted by an Observable‘s stream of values.

Let's imagine for a moment that we want to capitalize our movie titles and that such is an expensive operation.

Simply put, we can use forkEach() to off-load the execution of each onto a thread pool:

Observable<Movie> movieObs = movieSvc.getMovies();
Observable<String> upperCasedNames = movieObs.compose(RxRatpack::forkEach)
  .map(movie -> movie.getName().toUpperCase())

7. Implicit Error Handling

Lastly, implicit error handling is one of the key features in RxJava integration.

By default, RxJava observable sequences will forward any exception to the execution context exception handler. For this reason, error handlers don't need to be defined in observable sequences.

So, we can configure Ratpack to handle these errors raised by RxJava.

Let's say, for example, that we wanted each error to be printed in the HTTP response.

Note that the exception we throw via the Observable gets caught and handled by our ServerErrorHandler:

RatpackServer.start(def -> def.registryOf(regSpec -> regSpec
  .add(ServerErrorHandler.class, (ctx, throwable) -> {
        ctx.render("Error caught by handler : " + throwable.getMessage());
  .handlers(chain -> chain
    .get("error", ctx -> {
        Observable.<String> error(new Exception("Error from observable")).subscribe(s -> {});

Note that any subscriber-level error handling takes precedence, though. If our Observable wanted to do its own error handling, it could, but since it doesn't, the exception percolates up to Ratpack.

8. Conclusion

In this article, we talked about how to configure RxJava with Ratpack.

We explored the conversions of Observables in RxJava to Promise types in Ratpack and vice versa. We also looked into the parallelism and implicit error handling features supported by the integration.

All code samples used for the article can be found over at Github.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

Comments are closed on this article!