A demo project showcasing Apache Kafka Share Groups (queue semantics, KIP-932 / Kafka 4.x) integrated with the Quarkus framework via SmallRye Reactive Messaging.
The goal is to show how little code it takes to move from a classic consumer group to work-queue semantics — and how Quarkus Dev Services make the whole thing runnable with zero infrastructure setup.
All three consumers read the same Avro-serialized UserProfile records from the user-profile-avro topic. The differences are in how records are dispatched and acknowledged.
| Pattern | Channel | Class method |
|---|---|---|
| Classic consumer group | user-avro |
asConsumerGroup |
| Share group | user-avro-queue |
asShareGroup |
| Share group + manual acknowledgement | user-avro-queue-explicit |
asShareGroupExplicit |
Classic consumer group — the standard Kafka model. Partitions are statically assigned across group members; each partition is owned by exactly one consumer at a time. Scaling requires more partitions.
Share group — KIP-932 introduces work-queue semantics. Records are dynamically dispatched to whichever consumer in the group is available next. You get competing-consumer behavior without partition pinning or rebalances.
Share group with manual acknowledgement — extends share-group behavior with explicit accept() / reject() control per record. In this demo, records missing a secondaryEmail field are rejected so they can be redelivered to another consumer:
if (null == message.getSecondaryEmail()) {
ack.reject(); // redelivered to the next available consumer
} else {
processUserProfile(message);
ack.accept(); // acknowledged, will not be redelivered
}The data model is defined in src/main/avro/UserProfile.avsc. Quarkus generates Java classes from the schema at build time. The schema is registered with Schema Registry automatically — Apicurio Registry in dev mode, Confluent Schema Registry in Confluent Cloud mode.
UserProfileResource exposes two endpoints:
| Endpoint | Description |
|---|---|
GET /user-profile/send |
Generates 100 fake UserProfile records and publishes them to Kafka |
GET /user-profile/generate?count=N |
Returns N fake UserProfile records as JSON (without publishing) |
Records are generated with DataFaker and include realistic fields: username, location, roles, account status, signup timestamp, and an optional secondaryEmail (used to drive the explicit-ack consumer's accept/reject logic).
All common tasks are available via make. Run make help to see the full list.
Quarkus Dev Services spin up a Kafka broker and an Apicurio Registry container automatically — no Docker Compose, no local Kafka installation required. The Kafka container is pre-configured with share-group support enabled.
make dev-localThe Quarkus Dev UI is available at http://localhost:8080/q/dev/ and lets you inspect channels, message flows, and the schema registry without leaving your browser.
Once the app is running, produce some events:
make send # publish 100 UserProfile records to Kafka
make generate # preview 10 records as JSON without publishingconfluent loginThe terraform/ directory provisions a complete Confluent Cloud environment:
- A Dedicated Kafka cluster (1 CKU) with Share Group coordinator protocols enabled
- Schema Registry (Advanced package) with the
UserProfileAvro schema registered - Service accounts and API keys for Kafka and Schema Registry access
make cc-provision runs terraform init and terraform apply, then automatically writes all connection variables to a .env file at the project root.
make cc-provisionmake dev-cc validates that .env is present and fully populated before starting, then activates the confluent-cloud Quarkus profile, which switches the connector to SASL_SSL and points Schema Registry at Confluent Cloud. Dev Services are disabled in this profile.
make dev-ccWhen you're done, make cc-destroy prompts for confirmation, destroys the Confluent Cloud environment, and cleans up all local Terraform state and the .env file. It will refuse to run if the Quarkus app is still running on port 8080.
make cc-destroy# Standard JVM build
./gradlew build
# Über-jar
./gradlew build -Dquarkus.package.jar.type=uber-jar
# Native executable (requires GraalVM)
./gradlew build -Dquarkus.native.enabled=true
# Native executable via container (no GraalVM required)
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true