Quarkus Superheroes Sample
June 5, 2026 ยท View on GitHub
Table of Contents
- Introduction
- Project automation
- Running Locally via Docker Compose
- Deploying to Kubernetes
- Deploying to Azure Container Apps
Introduction
This is a sample application demonstrating Quarkus features and best practices. The application allows superheroes to fight against supervillains. The application consists of several microservices, communicating either synchronously via REST or asynchronously using Kafka. All the data used by the applications are on the characterdata branch of this repository.
This is NOT the workshop with the step-by-step instructions. If you are looking for the Quarkus Super Heroes workshop, you can find it at https://quarkus.io/quarkus-workshops/super-heroes/.
This is NOT a single multi-module project. Each service in the system is its own sub-directory of this parent directory. As such, each individual service needs to be run on its own.
The base JVM version for all the applications is Java 21.
- Super Hero Battle UI
- A React application to pick up a random superhero, a random supervillain, a random location, and makes them fight. Then optionally use AI to perform a narration of the fight.
- Served with Quarkus Quinoa
- Villain REST API
- A classical HTTP microservice exposing CRUD operations on Villains, stored in a PostgreSQL database.
- Implemented with blocking endpoints using RESTEasy Reactive and Quarkus Hibernate ORM with Panache's active record pattern.
- Favors field injection of beans (
@Injectannotation) over construction injection. - Uses the Quarkus Qute templating engine for its UI.
- Contains contract verification tests using Pact.
- Hero REST API
- A reactive HTTP microservice exposing CRUD operations on Heroes, stored in a PostgreSQL database.
- Implemented with reactive endpoints using RESTEasy Reactive and Quarkus Hibernate Reactive with Panache's repository pattern.
- Favors constructor injection of beans over field injection (
@Injectannotation). - Uses the Quarkus Qute templating engine for its UI.
- Contains contract verification tests using Pact.
- Narration REST API
- A blocking HTTP microservice integrating with OpenAI or Azure OpenAI Service to narrate a fight.
- Implemented with blocking endpoints using RESTEasy Reactive.
- Favors constructor injection of beans over field injection (
@Injectannotation). - Contains contract verification tests using Pact.
- Uses the Quarkus WireMock extension in development mode so that live calls that cost real money are not being made.
- Location gRPC API
- A blocking microservice with gRPC operations exposing CRUD operations on Locations, stored in a MariaDB database.
- Completely written in Kotlin.
- Implemented with blocking endpoints using Quarkus Hibernate ORM with Panache and Kotlin's repository pattern.
- Favors constructor injection of beans over field injection (
@Injectannotation).
- Fight REST API
- A REST API invoking the Hero and Villain APIs to get a random superhero and supervillain. Each fight is then stored in a MongoDB database.
- Invokes the Narration API to narrate the result of a fight.
- Implemented with reactive endpoints using RESTEasy Reactive and Quarkus MongoDB Reactive with Panache's active record pattern.
- Invocations to the Hero and Villain APIs are done using the reactive rest client and are protected using resilience patterns, such as retry, timeout, and circuit breaking.
- Each fight is asynchronously sent, via Kafka, to the Statistics microservice.
- Messages on Kafka use Apache Avro schemas and are stored in an Apicurio Registry, all using built-in support from Quarkus.
- Contains consumer contract and contract verification tests using Pact.
- Statistics
- Calculates statistics about each fight and serves them to an HTML + JQuery UI using WebSockets.
- Grafana LGTM Stack
- All services export traces, metrics, and logs via OpenTelemetry (OTLP) to the Grafana LGTM stack (Loki, Grafana, Tempo, Prometheus).
Here is an architecture diagram of the application:

The main UI allows you to pick one random Hero and Villain by clicking on New Fighters. Then, click Fight! to start the battle. The table at the bottom shows the list of previous fights.
You can then click the Narrate Fight button if you want to perform a narration using the Narration Service.
Caution
Using Azure OpenAI or OpenAI may not be a free resource for you, so please understand this! Unless configured otherwise, the Narration Service does NOT communicate with any external service. Instead, by default, it just returns a default narration. See the Integration with OpenAI Providers for more details.

https://github.com/quarkusio/quarkus-super-heroes/assets/363447/55a0a63f-c636-4719-9a7b-9a9034116e77
Running Locally via Docker Compose
Pre-built images for all of the applications in the system can be found at quay.io/quarkus-super-heroes.
Pick one of the 4 versions of the application from the table below and execute the appropriate docker compose command from the quarkus-super-heroes directory.
Note
You may see errors as the applications start up. This may happen if an application completes startup before one of its required services (i.e. database, kafka, etc). This is fine. Once everything completes startup things will work fine.
There is a watch-services.sh script that can be run in a separate terminal that will watch the startup of all the services and report when they are all up and ready to serve requests.
Run scripts/watch-services.sh -h for details about its usage.
| Description | Image Tag | Docker Compose Run Command | Docker Compose Run Command with Monitoring |
|---|---|---|---|
| JVM Java 21 | java21-latest | docker compose -f deploy/docker-compose/java21.yml up --remove-orphans | docker compose -f deploy/docker-compose/java21.yml -f deploy/docker-compose/monitoring.yml up --remove-orphans |
| Native | native-latest | docker compose -f deploy/docker-compose/native.yml up --remove-orphans | docker compose -f deploy/docker-compose/native.yml -f deploy/docker-compose/monitoring.yml up --remove-orphans |
Tip
If your system does not have the compose sub-command, you can try the above commands with the docker-compose command instead of docker compose.
Once started the main application will be exposed at http://localhost:8080. If you want to watch the Event Statistics UI, that will be available at http://localhost:8085. The Apicurio Registry will be available at http://localhost:8086.
If you launched the monitoring stack, Grafana will be available at http://localhost:3000.
Deploying to Kubernetes
Pre-built images for all of the applications in the system can be found at quay.io/quarkus-super-heroes.
Deployment descriptors for these images are provided in the deploy/k8s directory. There are versions for OpenShift, Minikube, Kubernetes, and Knative.
Note
The Knative variant can be used on any Knative installation that runs on top of Kubernetes or OpenShift. For OpenShift, you need OpenShift Serverless installed from the OpenShift operator catalog. Using Knative has the benefit that services are scaled down to zero replicas when they are not used.
The only real difference between the Minikube and Kubernetes descriptors is that all the application Services in the Minikube descriptors use type: NodePort so that a list of all the applications can be obtained simply by running minikube service list.
Note
If you'd like to deploy each application directly from source to Kubernetes, please follow the guide located within each application's folder (i.e. event-statistics, rest-fights, rest-heroes, rest-villains, rest-narration, grpc-locations).
Routing
Both the Minikube and Kubernetes descriptors also assume there is an Ingress Controller installed and configured. There is a single Ingress in the Minikube and Kubernetes descriptors denoting / and /api/fights paths. You may need to add/update the host field in the Ingress as well in order for things to work.
Both the ui-super-heroes and the rest-fights applications need to be exposed from outside the cluster. On Minikube and Kubernetes, the ui-super-heroes Angular application communicates back to the same host and port as where it was launched from under the /api/fights path. See the routing section in the UI project for more details.
On OpenShift, the URL containing the ui-super-heroes host name is replaced with rest-fights. This is because the OpenShift descriptors use Route objects for gaining external access to the application. In most cases, no manual updating of the OpenShift descriptors is needed before deploying the system. Everything should work as-is.
Additionally, there is also a Route for the event-statistics application. On Minikube or Kubernetes, you will need to expose the event-statistics application, either by using an Ingress or doing a kubectl port-forward. The event-statistics application runs on port 8085.
Versions
Pick one of the 4 versions of the system from the table below and deploy the appropriate descriptor from the deploy/k8s directory. Each descriptor contains all of the resources needed to deploy a particular version of the entire system.
Warning
These descriptors are NOT considered to be production-ready. They are basic enough to deploy and run the system with as little configuration as possible. The databases, Kafka broker, and schema registry deployed are not highly-available and do not use any Kubernetes operators for management or monitoring. They also only use ephemeral storage.
For production-ready Kafka brokers, please see the Strimzi documentation for how to properly deploy and configure production-ready Kafka brokers on Kubernetes. You can also try out a fully hosted and managed Kafka service!
For a production-ready Apicurio Schema Registry, please see the Apicurio Registry Operator documentation. You can also try out a fully hosted and managed Schema Registry service!
| Description | Image Tag | OpenShift Descriptor | Minikube Descriptor | Kubernetes Descriptor | Knative Descriptor |
|---|---|---|---|---|---|
| JVM Java 21 | java21-latest | java21-openshift.yml | java21-minikube.yml | java21-kubernetes.yml | java21-knative.yml |
| Native | native-latest | native-openshift.yml | native-minikube.yml | native-kubernetes.yml | native-knative.yml |
Monitoring
There are also Kubernetes deployment descriptors for monitoring with the Grafana LGTM stack (Grafana, Loki, Tempo, Prometheus) in the deploy/k8s directory (monitoring-openshift.yml, monitoring-minikube.yml, monitoring-kubernetes.yml). Each descriptor contains the resources necessary to monitor and gather metrics, traces, and logs from all of the applications in the system. Deploy the appropriate descriptor to your cluster if you want it.
The OpenShift descriptor will automatically create a Route for Grafana. On Kubernetes/Minikube you may need to expose the Grafana service in order to access it from outside your cluster, either by using an Ingress or by using kubectl port-forward. On Minikube, the Grafana Service is also exposed as a NodePort.
Warning
These descriptors are NOT considered to be production-ready. They are basic enough to deploy the Grafana LGTM stack with as little configuration as possible. It is not highly-available and does not use any Kubernetes operators for management or monitoring. It also only uses ephemeral storage.
Observability with Grafana
By now you've performed a few battles, so let's analyze the telemetry data.
Open the Grafana UI based on how you are running the system, either through Docker Compose (http://localhost:3000) or by deploying the monitoring stack to Kubernetes.
All services export traces, metrics, and logs via OpenTelemetry (OTLP) to the Grafana LGTM stack. In the Grafana UI you can:
- Explore traces using the Tempo data source
- View metrics using the pre-provisioned "Quarkus Micrometer OpenTelemetry" dashboard
- Search logs using the Loki data source, with automatic log-to-trace correlation