Sending a Hello from Mutiny in Wildfly

MicroProfile Reactive Messaging and SmallRye Mutiny

Recently I was learning about Mutiny to show how to use it together with MicroProfile Reactive Messaging (a.k.a. MP Messaging) in a presentation at work (Worldline Switzerland), and many Mutiny examples are using Quarkus. But we’re using not only Quarkus, there are still many of our projects relying on Wildfly, so I tried to get the examples running with Wildfly.

There is a Wildfly Blog post by Kabir Khan about using MicroProfile Reactive Messaging with Kafka but it is using the @Channel annotation together an Emitter (and not Mutiny):

@Channel("to-kafka")
private Emitter<String> emitter;

My idea was to use the @Outgoing annotation together with a Multi as shown in this example from the SmallRye Reactive Messaging Documentation

@Outgoing("prices-out")
public Multi<Double> generate() {
    return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
        .map(x -> random.nextDouble());
}

Mutiny

Mutiny 2 is a library for reactive programming, based on the Java 9 Flow API.

Basically the library contains just two reactive types Multi and Uni (and the name Mutiny is a contraction of multi and uni).

The Multi type is what the example above uses to generate a stream with many items.

Using Wildfly

I wanted to keep this example application very minimal, so there is no REST endpoint to observe it, and the solution needs 5 files

  • two Java classes (an “outgoing” one and an “incoming” one)
  • the MicroProfile configuration file
  • the Wildfly configuration file
  • the Maven POM to build and deploy the application

You can find the example application on GitHub.

Ticker.java – Populating a Channel

After a 5 second startup delay, the Ticker class sends out every second an incrementing number (the “message”) to a channel (named to-kafka):

@Outgoing("to-kafka")
public Multi<String> generate() {
    return Multi
            .createFrom().ticks()
            .startingAfter(Duration.ofSeconds(5L))
            .every(Duration.ofSeconds(1L))
            .onItem().transform(i -> Long.toString(i));
}

Once again the Mutiny type Multi is used to generate the stream of incrementing numbers.

Note that methods with an @Outgoing or @Incoming annotation are automatically called by the Reactive Messaging framework, you should not call them from your own code.

Listener.java – Consume a Channel

Of course there should be also a place where the numbers that are sent out in the Ticker, are received and consumed, using an @Incoming channel. This happens in the Listener class (nothing fancy, just writing out the received number):

@Incoming("from-kafka")
public void receive(String feed) {
    System.out.printf("Hello Wildfly from Mutiny Ticker %s%n", feed);
}

Connecting the Channels to a Kafka Topic

Until now we have two channels (they are separate, as they have different names), but they are kind of hanging in the air (not connected to anything)

To connect the channels to a Kafka topic, MP Messaging uses MP Config, usually by means of a microprofile-config.properties file:

1
2
3
4
5
6
7
8
9
mp.messaging.connector.smallrye-kafka.bootstrap.servers=localhost:9092

mp.messaging.outgoing.to-kafka.connector=smallrye-kafka
mp.messaging.outgoing.to-kafka.topic=hello-wildfly
mp.messaging.outgoing.to-kafka.value.serializer=org.apache.kafka.common.serialization.StringSerializer

mp.messaging.incoming.from-kafka.connector=smallrye-kafka
mp.messaging.incoming.from-kafka.topic=hello-wildfly
mp.messaging.incoming.from-kafka.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer

Line 4 tells that the channel to_kafka writes to the Kafka topic hello-wildfly and line 8 that the channel from_kafka consumes from the same topic:

Configuring Wildfly

The configuration is based on the standalone-microprofile.xml that comes with Wildfly; but for Reactive Messaging two subsystems must explicitly be enabled:

  • MicroProfile Reactive Messaging subsystem
  • MicroProfile Reactive Streams Operators subsystem

I copied standalone-microprofile.xml into the project and then added some lines to enable the additional subsystems (Kabir Khan shows in his blog the Wildfly CLI commands that have the same result).

In the <extensions> part of standalone.xml add two lines for the two MP Reactive extensions reactive-messaging-smallrye and reactive-streams-operators-smallrye:

35        <extension module="org.wildfly.extension.microprofile.jwt-smallrye"/>
36        <extension module="org.wildfly.extension.microprofile.openapi-smallrye"/>
37        <extension module="org.wildfly.extension.microprofile.reactive-messaging-smallrye"/>
38        <extension module="org.wildfly.extension.microprofile.reactive-streams-operators-smallrye"/>
39        <extension module="org.wildfly.extension.microprofile.telemetry"/>

and then further down add two lines in the <profiles> section for the subsystems microprofile-reactive-messaging-smallrye and microprofile-reactive-streams-operators-smallrye:

340        <subsystem xmlns="urn:wildfly:microprofile-jwt-smallrye:1.0"/>
341        <subsystem xmlns="urn:wildfly:microprofile-openapi-smallrye:1.0"/>
342        <subsystem xmlns="urn:wildfly:microprofile-reactive-messaging-smallrye:1.0"/>
343        <subsystem xmlns="urn:wildfly:microprofile-reactive-streams-operators-smallrye:1.0"/>
344        <subsystem xmlns="urn:wildfly:microprofile-telemetry:1.0"/>

When you use a standalone.xml file in your project (as opposed to in the standalone/configuration directory of the Wildfly installation), you’ll have to start Wildfly slightly differently to tell it where to find to configuration file, see below.

Maven

You need three dependencies in the pom.xml file

    <dependencies>
        <!-- actually needed EE and MP dependencies, from the Wildfly MP BOM -->
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.microprofile.reactive.messaging</groupId>
            <artifactId>microprofile-reactive-messaging-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- dependencies needed in the WAR -->
        <dependency>
            <!-- The version is managed by Wildfly's BOM -->
            <groupId>io.smallrye.reactive</groupId>
            <artifactId>mutiny</artifactId>
        </dependency>
    </dependencies>

Note that jakarta.enterprise.cdi-api and microprofile-reactive-messaging-api are part of Wildfly “public modules” and managed by the Wildfly MicroProfile BOM, so you should use provided scope and not specify a version.

But the io.smallrye.reactive:mutiny dependency is, although shipped with Wildfly, only used internally and not visible for applications, so you must not use scope provided. It’s version is still managed by the Wildfly BOM though, no need to specify the version yourself.

There is another part of the pom.xml that I want to mention:

    <plugin>
        <groupId>org.wildfly.plugins</groupId>
        <artifactId>wildfly-maven-plugin</artifactId>
        <version>${wildfly.plugin.version}</version>
    </plugin>

The Wildfly Maven Plugin is used in the instructions below to deploy the application into a running Wildfly server, but that is only scratching on the surface of its possibilities.

Run the Example

Prerequisites: Java 17, Maven (3.8.x works fine) and either Kafka or Docker (to run Kafka in a container).

I’m using two command line sessions. One to host the Wildfly server and the other to compile and control the application.

First session

  • Download the latest Wildfly (Wildfly 28 as of May 2023) and unpack it into a directory of your choice.

  • Clone the repository with the source code

    Cloning the repo in session 1
    • bash
    • cmd
    1
    2
    3
      git clone https://github.com/pe-st/wildfly-hello-mutiny.git
      cd wildfly-hello-mutiny
      
  • Start a local Kafka if you don’t have one running anyway. I’m using docker for that but that’s up to you. You can also use a remote Kafka if you adapt the project’s microprofile-config.properties accordingly.

    The project provides a docker-compose.yml file which makes starting Kafka using docker easy:

    Session 1, still in directory wildfly-hello-mutiny
    • all
    1
    2
    3
      # the '--ansi never' is optional, for better contrast (YMMV)
      docker compose --ansi never up -d
      
  • Start Wildfly

    Session 1, still in directory wildfly-hello-mutiny
    • bash
    • cmd
    1
    2
    3
    4
      # use the directory where you unpacked Wildfly
      export WILDFLY_HOME=~/Documents/wildfly-28.0.0.Final
      $WILDFLY_HOME/bin/standalone.sh --read-only-server-config=src/main/wildfly/standalone.xml
      

    Note the additional parameter to specify the standalone.xml from the project and not the Wildfly standard one.

Second session

Open a second command line session (in the first one wildfly is running in the foreground)

  • compile the application:

    cd wildfly-hello-mutiny
    mvn package
    
  • deploy the application to Wildfly (using the Wildfly Maven Plugin)

    mvn wildfly:deploy
    # after a couple of seconds you should see this in the output from Maven
    [INFO] --- wildfly-maven-plugin:4.1.0.Final:deploy (default-cli) @ wildfly-hello-mutiny ---
    [INFO] JBoss Threads version 2.4.0.Final
    [INFO] JBoss Remoting version 5.0.27.Final
    [INFO] XNIO version 3.8.9.Final
    [INFO] XNIO NIO Implementation Version 3.8.9.Final
    [INFO] ELY00001: WildFly Elytron version 2.1.0.Final
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    
  • Now switch to the first session and observe the output. After some seconds you’ll see this output:

    (Kafka initialization messages)
    17:27:49,907 INFO  [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 0
    17:27:49,911 INFO  [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 1
    17:27:50,893 INFO  [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 2
    17:27:51,892 INFO  [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 3
    17:27:52,892 INFO  [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 4
    ...
    
  • stop the application

    mvn wildfly:undeploy
    

Conclusion

With the latest Wildfly 28 it has become quite straightforward to use Mutiny and MicroProfile Reactive Messaging. It is not provided yet in the standard configurations, but it takes only two steps:

  • enable the two Wildfly extensions/subsystems for Reactive Messaging
  • the Mutiny dependency in pom.xml must not use scope provided

There is a minimal example project that shows how to target Wildfly with an application using Mutiny.

Peter Steiner

Software Developer and Opinionated Citizen

Switzerland