A Simple Guide to Reactive Java with Spring Webflux
Tags: code, javasdk, springboot
Published: 2021-02-06 01:50:38

---
title: A Simple Guide to Reactive Java with Spring Webflux
published: true
date: 2020-12-10 01:20:00 UTC
tags: Code,javasdk,springboot
canonical_url: https://www.split.io/blog/reactive-java-spring-webflux/
---

In this tutorial, you will create a reactive web service using Spring Boot and Spring Webflux. The web service will demonstrate how to use feature flags in a reactive environment using Split’s Java SDK. You’ll use the Spring Initializr project to bootstrap your application with the necessary configuration parameters quickly. The simple application you’ll build will expose a resource that streams words once a second from a paragraph from James Baldwin’s “A Letter to my Nephew.”

As I mentioned, this project will be _reactive_. That certainly sounds exciting, but what does it mean? This is honestly a big topic that can’t be explained in full detail here. I’m just going to give an overview.

## Reactive Programming in Java

Reactive programming is a programming paradigm that is built around stream processing. Data is handled as a stream of data points passing through a pipeline of functions or operators. Each operator can transform and manipulate the data before it is passed on to the next operator. You may be familiar with operators like `map`, `collect`, and `reduce`. These operations are chained until the transformed data is returned. Reactive is in contrast to imperative code where a program state is maintained, and functions or methods operate on the state, mutating it.

Reactive programming is also non-blocking and asynchronous. This means that it is designed to work with single-threaded, non-blocking servers like Netty, Undertow, and Node.js. Non-blocking code is another HUGE topic but stated simply, it is code that never stops and waits for something to finish–such as reading from a disk or making a network call–“blocking” the thread. Instead, callbacks and promises are used to defer code execution until the input data is ready, allowing the thread to continue processing other work.

Ideally, this allows for super high performance because the server can do all of its work on a single thread and doesn’t waste time and resources managing a rotating stack of worker threads that the processor has to constantly switch between. As with all things, however, there are trade-offs.

For example, non-blocking code has historically been harder to implement, maintain, and debug (search the internet for “callback hell”). This has changed to some degree as the technology has matured. The world, however, is rarely ideal. Not all non-blocking code is non-blocking, and one piece of poorly written code in a dependency can start blocking your entire web service, cratering your performance. But when done right, reactive non-blocking code can be a great tool. One use case in which non-blocking, reactive code excels is handling streams.

Spring Webflux is Spring Boot’s reactive, non-blocking web application implementation. It uses Project Reactor for its reactive Java implementation and, by default, Netty for its non-blocking web server implementation. Webflux actually supports other web server implementations, such as Tomcat, Jetty, Undertow, and Servlet 3.1+ containers. Why anyone would use Tomcat or Jetty with Webflux (using a blocking servlet container with a reactive, asynchronous framework) is a great and open question, but you have that option.

Spring Webflux is implicitly in contrast to Spring MVC. Spring MVC is the good, old-fashioned servlet-based Java server. It’s a multi-threaded, concurrent system that allows blocking, synchronous code. Instead of using callbacks and promises to handle concurrency, it assumes that each request will happen in its own thread (and may potentially launch extra worker threads as needed). There is, in fact, absolutely nothing wrong with this paradigm or technology. For every benchmark article claiming non-blocking is faster than blocking, you can find another one claiming the opposite. Well written blocking code can be just as fast as non-blocking code, generally speaking. A well-designed and properly administered system is going to beat a poorly thought out system that isn’t being maintained or administered correctly.

## Get Started with Feature Flags in Java

The last topic before you dive into the code is feature flags. In a traditional development environment, if you want to make updates or changes to your deployed application, you change the code, package it, deploy it, and make a release. This is slow. Feature flags are a way to make the release process far more dynamic. In essence, they are dynamic state variables checked at runtime to determine how the code behaves _while deployed._ The flag’s state lives on a server, and the state can be updated in real-time. You write your code in logical chunks whose state is determined by the flags.

Using this system, you could, for example, roll out a new feature to a small portion of your user base as an alpha test. Once that is passed, you could roll it out to a larger portion as a beta. If the beta fails, you could roll all the users back. If it passes, you could finish the roll out to all of your users.

Another idea would be to use them for A/B testing. That is, dynamically segment users and roll out one set of features to one set of users and another set of features to a different group.

In this tutorial, you will use Split as the feature flag service provider. Split offers a free account that will allow you to create feature flags and integrate them into the Spring Boot application using Split’s Java SDK.

Since this tutorial focuses on reactive programming with Spring Boot and Spring Webflux, I want to point you to a couple of general resources on using feature flags with Spring Boot. This post [gives a general overview of using feature flags with Spring Boot](https://www.split.io/blog/spring-boot-feature-toggles/). This one demonstrates [how to integrate the Split SDK into the more traditional Spring MVC web server configuration](https://www.split.io/blog/spring-boot-app-feature-flags-example/).

## Dependencies

**Java** : I used Java 12 for this tutorial. You can download and install Java by going to [the AdaptOpenJdk website](https://adoptopenjdk.net/installation.html#). Or you can use a version manager like [SDKMAN](https://sdkman.io/) or even [Homebrew](https://brew.sh/).

**Split** : Sign up for a [free Split account](https://split.io/signup/) if you don’t already have one. This is how you’ll implement the feature flags.

**HTTPie** : This is a powerful command-line HTTP request utility you’ll use to test the reactive server. Install it according to [the docs on their site](https://httpie.org/doc#installation).

## Use Spring Initializr to Create Your Demo App

Spring has a project that helps create applications called the Spring Initializr. You’re going to use it to download a pre-configured starter project.

Open [this link](https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.4.0.RELEASE&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=devtools,webflux), which will load the correct configuration.

For the curious, you can accomplish the same thing as the link above from the command line:

```

`http https://start.spring.io/starter.tgz \
type==gradle-project \
language==java \
platformVersion==2.4.0.RELEASE \
packaging==jar \
javaVersion==11 \
groupId==com.example \
artifactId==demo \
name==demo \
description==Demo%20project%20for%20Spring%20Boot \
packageName==com.example.demo \
dependencies==devtools,webflux \
| tar -xzvf -`
<small id="shcb-language-14"><span>Code language:</span> <span>SQL (Structured Query Language)</span> <span>(</span><span>sql</span><span>)</span></small>
```

The most important thing is it’s configuring the `webflux` dependency. You can look at [the Spring Initializr GitHub page](https://github.com/spring-io/initializr) if you want to learn more about the configuration options.

One cool feature I’ll point out is the **EXPLORE** button at the bottom of the page. Click that, and you can dig through a preview of the project online.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaSpringInit-859x1024.png?width=859)

Click the **GENERATE** button. Download the `demo.zip` file and unzip it somewhere appropriate.

## Create a Reactive Streaming Server

At this point, you have a fully functional reactive Spring Boot application that does absolutely nothing. If you examine the project at this point, you’ll see that there’s not much to it. There’s basically a `DemoApplication.java` file.

`src/main/java/com/example/demo/DemoApplication.java`:

```

`@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}`
<small id="shcb-language-15"><span>Code language:</span> <span>Java</span> <span>(</span><span>java</span><span>)</span></small>
```

Of course, this simplicity hides a ton of complexity on the back end. Spring has configured an application for you that uses Netty (this is the default – there are other options, like Undertow) as the webserver engine and supports the Java Reactor framework for reactive Java programming. And all of this has been folded into the Spring programming framework to be highly configurable and extensible.

To make life more exciting, you should probably have the demo application do something. In this case, you’re going to have it live stream a speech, emitting a new word every second.

Create a new Java file called `DemoResource.java`.

`src/main/java/com/example/demo/DemoResource.java`:

```

`package com.example.demo;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.time.Duration;

@RestController
public class DemoResource {

    // Excerpt of James Baldwin "A Letter to my Nephew"
    private String[] speech = {
            "Well,","you","were","born;","here","you","came,","something","like","fifteen","years","ago,","and","though",
            "your","father","and","mother","and","grandmother,","looking","about","the","streets","through","which","they",
            "were","carrying","you,","staring","at","the","walls","into","which","they","brought","you,","had","every",
            "reason","to","be","heavy-hearted,","yet","they","were","not,","for","here","you","were,","big","James,",
            "named","for","me.","You","were","a","big","baby.","I","was","not.","Here","you","were","to","be","loved.",
            "To","be","loved,","baby,","hard","at","once","and","forever","to","strengthen","you","against","the",
            "loveless","world.","Remember","that.","I","know","how","black","it","looks","today","for","you.","It","looked",
            "black","that","day","too.","Yes,","we","were","trembling.","We","have","not","stopped","trembling","yet,",
            "but","if","we","had","not","loved","each","other,","none","of","us","would","have","survived,","and","now",
            "you","must","survive","because","we","love","you","and","for","the","sake","of","your","children","and",
            "your","children's","children.",
    };

    @GetMapping(value="/speech", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> getSpeech() {
        return Flux
                .fromArray(speech)
                .delayElements(Duration.ofSeconds(1))
                .repeat()
                .log();
    }
}`
<small id="shcb-language-16"><span>Code language:</span> <span>Swift</span> <span>(</span><span>swift</span><span>)</span></small>
```

From a shell, start the Spring Boot app.

```

`./gradlew bootRun`

```

Once the app has finished loading from a second shell, use HTTPie to test the endpoint. Notice the `--stream` parameter. Pretty sweet that HTTPie supports streaming endpoints.

```

`http :8080/speech --stream`
<small id="shcb-language-17"><span>Code language:</span> <span>SQL (Structured Query Language)</span> <span>(</span><span>sql</span><span>)</span></small>
```

You should see something like the following, with a word from James Baldwin’s “A Letter to my Nephew” streamed each second.

```

`HTTP/1.1 200 OK
Content-Type: text/event-stream;charset=UTF-8
transfer-encoding: chunked

data:Well,

data:you

data:were`
<small id="shcb-language-18"><span>Code language:</span> <span>PowerShell</span> <span>(</span><span>powershell</span><span>)</span></small>
```

## Create a Feature Flag Treatment

Before you change the code to use the Java Split SDK to react to treatments, you need to create a treatment in your Split account. If splits and treatments are new to you, I’d suggest reading through [the getting started information on Split’s website](https://help.split.io/hc/en-us/articles/360025035812-Introduction-getting-started).

Briefly stated, a **split** is a decision point in code that can be used to modify the code in real-time _without rebuilding and redeploying the code_. It’s a flag that you define in your code that allows you to change how your code works through the Split dashboard instead of pushing updates to the app. A **treatment** is the specific state of these real-time flags. The `SplitClient` retrieves the treatment when you call the `getTreatment(String key, String splitName)` method, based on the **key** and the split’s name. The key could be any arbitrary, unique string. But most often might be the username of the current user.

| **Pro Tip:** There’s an optional third `getTreatment` parameter (which we won’t use here), which is an **attribute map** object containing user attributes in name-value pairs. Even sensitive user data can be passed in this map because none of it is sent to Split’s cloud. Instead, the **attribute map** is compared in local memory to targeting rules you have entered in the Split UI. More in the Split SDK docs: [custom targeting with attribute maps](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK#attribute-syntax). |

Using Split and feature flags, you could, for example, dynamically test a set of new features on different subsets of users, and depending on how the testing went, you could move the features to a full roll-out or roll them back, all without having to redeploy the app.

I glossed over a simplification above. I said that the `SplitClient.getTreatment()` method retrieves the treatment. When the client is created, the possible treatment values are cached, and a socket connected to Split is opened. If the treatment is updated on the Split servers, the socket is used to update the cached values. In this way, the `getTreatment()` method is much more performant than if it had to make an actual network request to the Split servers and updates to the treatments can be pushed in real-time (theoretically real-time, in practice I noticed a lag of about 10-20 seconds between updates on the dashboard and those updates being reflected locally).

You’re going to create a split named **speech**. The split’s treatment will have three possible values: `a`, `b`, and `off`. A user’s name will determine which treatment is returned (passed in as a query param to the resources server, for simplicity).

Open the [Split dashboard](https://app.split.io). You should be in the **Default** workspace.

Click on the blue **Create Split** button.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaCreateSplitButton-798x1024.png?width=798)

Give the split a **name** : `speech`. You can leave the rest blank.

Click **Create**.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaCreateSplitDialog-1024x977.png?width=1024)

You should be in the `staging-default` environment for the new split.

Click the **Add Rules** button. Split adds `on` and `off` by default. Click the **Add Treatment** button to add a new value. Change the labels for the three treatment values to `a`, `b`, and `off`.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaAddTreatment-1024x518.png?width=1024)

Now you want to define two targeting rules. The first rule will tell Split that if the user’s name is `bob` or `fred`, it should serve treatment `a`.

Scroll down until you see the **Set targeting rules** section. Click **Add rule**. Click the drop drown that says **is in segment** , hover on **string** , and click on **is in list**. In the text box to the right, enter two names: `bob` and `fred`. Ensure the **serve** drop down (bottom right of the gray box for the rule) shows the `a` treatment.

The second rule will tell Split that if the user’s name is `wilma` or `ted`, it should serve treatment `b`.

Click **Add rule**. Click the drop drown that says **is in segment** , hover on **string** , and click on **is in list**. In the text box to the right, enter two names: `wilma` and `ted`. Make sure the **serve** drop-down shows the `b` treatment.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaAddRule-1024x394.png?width=1024)

Below that, you want to make the default behavior for any other users to be `off`. Under the next section, **Set the default rule** , change the **serve** drop-down to `off`. Likewise, in the following section, **Set the default treatment** , select the `off` treatment.

![](https://www.split.io/wp-content/uploads/2021/01/reactiveJavaSetDefaultTreatment-1024x399.png?width=1024)

Click the blue **Save changes** button at the top of the screen. Scroll down and click **Confirm**.

That’s it on the Split dashboard. To summarize, you created a single split with three treatments. You created a couple of rules that assign treatment `a` to users named either `bob` or `fred` and treatment `b` to users named `wilma` or `ted`. All other users get the `off` treatment, which is also the default if there is a network problem or if the client is unable to retrieve a treatment for some other reason.

## Configure the Reactive Streaming Server to Use Feature Flag Treatments

Open the `build.gradle` file and add the Split SDK dependency.

```

`dependencies {
...
    compile 'io.split.client:java-client:4.1.0'
}`
<small id="shcb-language-19"><span>Code language:</span> <span>Bash</span> <span>(</span><span>bash</span><span>)</span></small>
```

Create a `SplitConfig` file that handles configuring the SplitClient bean, which will be injected into the `DemoResource`.

`src/main/java/com/example/demo/SplitConfig.java`

```

`@Configuration
public class SplitConfig {

    @Value("#{ @environment['split.api-key'] }")
    private String splitApiKey;

    @Bean
    public SplitClient splitClient() throws Exception {
        SplitClientConfig config = SplitClientConfig.builder()
                .setBlockUntilReadyTimeout(1000)
                .enableDebug()
                .build();

        SplitFactory splitFactory = SplitFactoryBuilder.build(splitApiKey, config);
        SplitClient client = splitFactory.client();
        client.blockUntilReady();

        return client;
    }

}`
<small id="shcb-language-20"><span>Code language:</span> <span>Java</span> <span>(</span><span>java</span><span>)</span></small>
```

You need your Split API key. From the Split dashboard, go to the square workspace icon in the top-left of the dashboard (which probably says **DE** for _default_) and click on it. Click on **Admin Settings**. Click on **API keys** in the panel on the left side, under **Workspace settings**.

There will be four API keys, two for production and two for staging. There are also different keys for server-side and client-side use. Copy the **SDK** and **staging-default** key.

Add it to your `application.properties`.

```

`split.api-key={yourSplitApiKey}`
<small id="shcb-language-21"><span>Code language:</span> <span>Swift</span> <span>(</span><span>swift</span><span>)</span></small>
```

Finally, update `DemoResource.java` to use the `SplitClient` to get the treatment and use it.

```

`@RestController
public class DemoResource {

    // Excerpt of James Baldwin "A Letter to my Nephew"
    private String[] speech = {
            "Well,","you","were","born;","here","you","came,","something","like","fifteen","years","ago,","and","though",
            "your","father","and","mother","and","grandmother,","looking","about","the","streets","through","which","they",
            "were","carrying","you,","staring","at","the","walls","into","which","they","brought","you,","had","every",
            "reason","to","be","heavy-hearted,","yet","they","were","not,","for","here","you","were,","big","James,",
            "named","for","me.","You","were","a","big","baby.","I","was","not.","Here","you","were","to","be","loved.",
            "To","be","loved,","baby,","hard","at","once","and","forever","to","strengthen","you","against","the",
            "loveless","world.","Remember","that.","I","know","how","black","it","looks","today","for","you.","It","looked",
            "black","that","day","too.","Yes,","we","were","trembling.","We","have","not","stopped","trembling","yet,",
            "but","if","we","had","not","loved","each","other,","none","of","us","would","have","survived,","and","now",
            "you","must","survive","because","we","love","you","and","for","the","sake","of","your","children","and",
            "your","children's","children.",
    };

    SplitClient splitClient;

    public DemoResource(SplitClient splitClient) {
        this.splitClient = splitClient;
    }

    @GetMapping(value="/speech", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<List<String>> getSpeech(@RequestParam String user) {
        return Flux
                .fromArray(speech)
                .publishOn(Schedulers.boundedElastic())
                .map(word -> {
                    String treatment = splitClient.getTreatment(user,"speech");
                    String treatedWord = word;
                    if (treatment.equals("a")) {
                        treatedWord = word.toUpperCase();
                    }
                    else if (treatment.equals("b")) {
                        treatedWord = word.toLowerCase();
                    }
                    return Arrays.asList(treatedWord, treatment);

                })
                .delayElements(Duration.ofSeconds(1))
                .repeat()
                .log();
    }
}`
<small id="shcb-language-22"><span>Code language:</span> <span>Arduino</span> <span>(</span><span>arduino</span><span>)</span></small>
```

Notice that the `getSpeech()` method now returns a list of strings. It returns the current word and the treatment. This is just for testing or demo purposes so that you can explicitly see what treatment is active.

Also, notice that you’re using Spring’s dependency injection to pass the `SplitClient` bean configured in the `SplitConfig` class to the `DemoResource` constructor.

```

`SplitClient splitClient;

public DemoResource(SplitClient splitClient) {
    this.splitClient = splitClient;
}`
<small id="shcb-language-23"><span>Code language:</span> <span>Arduino</span> <span>(</span><span>arduino</span><span>)</span></small>
```

Pay attention to the following line in the code from the `getSpeech()` method. It’s important.

```

`.publishOn(Schedulers.boundedElastic())`
<small id="shcb-language-24"><span>Code language:</span> <span>CSS</span> <span>(</span><span>css</span><span>)</span></small>
```

You have to use the `publishOn` operator because the `SplitClient.getTreatment()` method is blocking or synchronous. The `publishOn` operator pushes the execution thread to a new thread so that it doesn’t block one of the main threads used by the Netty server.

Remember that non-blocking, and asynchronous servers work using a single main thread (or a very small number of them). Blocking that thread **blocks the entire web server**. Don’t ever do this. If you block the main thread, your coworkers will hate you, your enemies will taunt you, and your friends will laugh at you. The high performance that non-blocking servers like Netty and Node.js can potentially achieve is largely a product of their not having to perform a lot of context switching between threads. However, this is contingent on code in the main thread behaving by _never blocking_ the thread but instead deferring execution using callbacks and promises.

When you want to use a blocking API in a non-blocking context, the only way to handle it is to push it off onto a worker thread. There’s a trade-off here because once you start pushing work off from the main thread onto other threads you start to reduce the efficiency of the non-blocking model. The more you do this, the more you start to look like a traditional web server. At some point, you would likely be better served by simply using a server like Tomcat (used by default in Spring MVC), which is highly optimized to handle a multi-threaded web environment. For a discussion of this, and more on handling blocking code in WebFlux, take a look at [this Spring blog post](https://spring.io/blog/2019/12/13/flight-of-the-flux-3-hopping-threads-and-schedulers).

Finally, take a look at the section of the `getSpeech()` method that has been added to retrieve and use the treatment.

```

`.map(word -> {
    String treatment = splitClient.getTreatment(user, "speech");
    String treatedWord = word;
    if (treatment.equals("a")) {
        treatedWord = word.toUpperCase();
    }
    else if (treatment.equals("b")) {
        treatedWord = word.toLowerCase();
    }
    return Arrays.asList(treatedWord, treatment);

})`
<small id="shcb-language-25"><span>Code language:</span> <span>Arduino</span> <span>(</span><span>arduino</span><span>)</span></small>
```

You see the `getTreatment()` call to the `SplitClient`. This is the blocking call that retrieves the treatment value. The treatment will be a string value, either `a`, `b`, or `off`.

After that, there is a pretty straightforward `if / else if` sequence that modifies the returned word based on the treatment and also returns the treatment itself.

## Test the Treatment

Try it out. Run the server from a shell.

```

`./gradlew bootRun`

```

Once that’s ready, make the streaming GET request.

```

`http :8080/speech user==bob --stream`
<small id="shcb-language-26"><span>Code language:</span> <span>SQL (Structured Query Language)</span> <span>(</span><span>sql</span><span>)</span></small>
```

You’ll see a stream with the words in all caps and a treatment value of `a`.

```

`HTTP/1.1 200 OK
Content-Type: text/event-stream;charset=UTF-8
transfer-encoding: chunked

data:["WELL,","a"]

data:["YOU","a"]

data:["WERE","a"]

data:["BORN;","a"]`
<small id="shcb-language-27"><span>Code language:</span> <span>PowerShell</span> <span>(</span><span>powershell</span><span>)</span></small>
```

Leave the Java server running but `control-c` to stop the HTTPie client. Rerun the client, passing in a new value for the user name.

```

` http :8080/speech user==wilma --stream`
<small id="shcb-language-28"><span>Code language:</span> <span>SQL (Structured Query Language)</span> <span>(</span><span>sql</span><span>)</span></small>
```

This time you’ll see treatment `b` and all lowercase.

```

`HTTP/1.1 200 OK
Content-Type: text/event-stream;charset=UTF-8
transfer-encoding: chunked

data:["well,","b"]

data:["you","b"]

data:["were","b"]

data:["born;","b"]`
<small id="shcb-language-29"><span>Code language:</span> <span>PowerShell</span> <span>(</span><span>powershell</span><span>)</span></small>
```

To see how these treatments can be manipulated in real-time, leave this running and go back to your Split dashboard. Open the `speech` split and scroll down to the **Set targeting rules** panel. In the second rule (the one with `wilma` and `ted`), switch the **serve** drop-down from `b` to `a`. At this point, nothing has changed. You have to click the **Save changes** button at the top of the screen. **Confirm** the changes.

Now watch the shell window with the streaming values. In a few seconds, it will change to reflect this update.

```

`data:["at","b"]

data:["once","b"]

data:["and","b"]

data:["FOREVER","a"]

data:["TO","a"]

data:["STRENGTHEN","a"]`
<small id="shcb-language-30"><span>Code language:</span> <span>CSS</span> <span>(</span><span>css</span><span>)</span></small>
```

Pretty cool. Real-time updates. Obviously, these examples are somewhat trivial, but once you understand the power of dynamic feature flags embedded in your code, there are all kinds of great things you can do with them.

## What You Learned: Spring Initializr, Reactive Java, Spring WebFlux, and Feature Flags

In this tutorial, you used the Spring Initializr to create a Reactive Java application using Spring Boot and Spring Webflux. The Java application generated a stream of words using reactive, non-blocking code. You used HTTPie to test this reactive, streaming server from the command line. You used Split to implement a feature flag in your code. You saw how to create a split, define treatments, and define targeting rules based on user attributes. Next, you used the Split Java SDK to integrate this feature flag into your reactive Java web application. You saw how to use the `publishOn` operator to integrate blocking code into a non-blocking web environment correctly. Finally, you saw how the treatment values and targeting rules could be updated in real-time on the Split dashboard.

You can find the completed source for the example from this post on [this](https://github.com/splitio-examples/split-webflux-example) GitHub repo.

### Learn More About Spring and Reactive Java

- [Spring’s Reactive Docs](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html)
- [Flux 3 Threads and Schedulers](https://spring.io/blog/2019/12/13/flight-of-the-flux-3-hopping-threads-and-schedulers)
- [Notes on Reactive Programming](https://spring.io/blog/2016/07/20/notes-on-reactive-programming-part-iii-a-simple-http-server-application)
- [Project Reactor Docs](https://projectreactor.io/docs/core/release/reference/#schedulers)

### Learn More About Feature Flags in Java

- [Get Started with Spring Boot and Vue.js](https://www.split.io/blog/vue-js-spring-boot-tutorial/)
- [Testing a Feature-flagged Change](https://www.split.io/blog/testing-feature-flagged-change/)
- [Leverage Spring Security to Test in Production](https://www.split.io/blog/spring-security-test-in-production/)
- [Build a CRUD App with Spring Boot and React in 20 Minutes](https://www.split.io/blog/tutorial-spring-boot-react/)
- [Monitor Your Java App with New Relic](https://www.split.io/blog/monitor-your-java-application-with-new-relic/)

And as always, we’d love to have you follow along as we build new content! You can find Split on [Twitter](https://twitter.com/SplitSoftware), [LinkedIn](https://www.linkedin.com/company/split-software/), and [YouTube](https://www.youtube.com/channel/UCrkFMZZY8cjmZyKzOVV245Q/videos)!