diff --git a/pom.xml b/pom.xml
index a82f898689..cfd70b4ffd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -703,6 +703,7 @@
osgi
spring-katharsis
logging-modules
+ spring-boot-documentation
spring-boot-modules
apache-httpclient
apache-httpclient4
@@ -974,6 +975,7 @@
osgi
spring-katharsis
logging-modules
+ spring-boot-documentation
spring-boot-modules
apache-httpclient
apache-httpclient4
diff --git a/spring-boot-documentation/pom.xml b/spring-boot-documentation/pom.xml
new file mode 100644
index 0000000000..d718f33a99
--- /dev/null
+++ b/spring-boot-documentation/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+ com.baeldung.spring-boot-documentation
+ spring-boot-documentation
+ 1.0.0-SNAPSHOT
+ spring-boot-documentation
+ pom
+
+
+ com.baeldung
+ parent-boot-3
+ 0.0.1-SNAPSHOT
+ ../parent-boot-3
+
+
+
+ springwolf
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit-jupiter.version}
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+
+ 3.3.2
+
+
+
diff --git a/spring-boot-documentation/springwolf/docker-compose.yml b/spring-boot-documentation/springwolf/docker-compose.yml
new file mode 100644
index 0000000000..214e2d2ace
--- /dev/null
+++ b/spring-boot-documentation/springwolf/docker-compose.yml
@@ -0,0 +1,37 @@
+version: '3'
+services:
+ zookeeper:
+ image: confluentinc/cp-zookeeper:latest
+ environment:
+ ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME:
+
+ kafka:
+ image: confluentinc/cp-kafka:latest
+ depends_on:
+ - zookeeper
+ ports:
+ - "9092:9092"
+ environment:
+ KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_HOST://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
+
+
+ akhq:
+ image: tchiotludo/akhq
+ restart: unless-stopped
+ environment:
+ AKHQ_CONFIGURATION: |
+ akhq:
+ connections:
+ docker-kafka-server:
+ properties:
+ bootstrap.servers: "kafka:29092"
+
+ ports:
+ - "9090:8080"
+ links:
+ - kafka
diff --git a/spring-boot-documentation/springwolf/pom.xml b/spring-boot-documentation/springwolf/pom.xml
new file mode 100644
index 0000000000..4bd9f24065
--- /dev/null
+++ b/spring-boot-documentation/springwolf/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+ springwolf
+ 0.0.1-SNAPSHOT
+ springwolf
+ Documentation Spring Event Driven API Using AsyncAPI and Springwolf
+
+
+ com.baeldung.spring-boot-documentation
+ spring-boot-documentation
+ 1.0.0-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.swagger.core.v3
+ swagger-core-jakarta
+ ${swagger-core.version}
+
+
+ io.github.springwolf
+ springwolf-kafka
+ ${springwolf-kafka.version}
+
+
+ io.github.springwolf
+ springwolf-ui
+ ${springwolf-ui.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainers-kafka.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.boot.documentation.springwolf.SpringwolfApplication
+
+
+
+
+
+
+ 2.2.11
+ 0.12.1
+ 0.8.0
+ 1.18.3
+
+
+
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/SpringwolfApplication.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/SpringwolfApplication.java
new file mode 100644
index 0000000000..1eed112a40
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/SpringwolfApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.boot.documentation.springwolf;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringwolfApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringwolfApplication.class, args);
+ }
+}
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/incoming/IncomingConsumer.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/incoming/IncomingConsumer.java
new file mode 100644
index 0000000000..ae201dbf30
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/incoming/IncomingConsumer.java
@@ -0,0 +1,48 @@
+package com.baeldung.boot.documentation.springwolf.adapter.incoming;
+
+import com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto;
+import com.baeldung.boot.documentation.springwolf.service.ProcessorService;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+import static org.springframework.kafka.support.mapping.AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME;
+
+@AllArgsConstructor
+@Component
+@Slf4j
+public class IncomingConsumer {
+
+ private static final String TOPIC_NAME = "incoming-topic";
+
+ private final ProcessorService processorService;
+
+ @KafkaListener(topics = TOPIC_NAME)
+ @AsyncListener(operation = @AsyncOperation(
+ channelName = TOPIC_NAME,
+ description = "More details for the incoming topic",
+ headers = @AsyncOperation.Headers(
+ schemaName = "SpringKafkaDefaultHeadersIncomingPayloadDto",
+ values = {
+ // this header is generated by Spring by default
+ @AsyncOperation.Headers.Header(
+ name = DEFAULT_CLASSID_FIELD_NAME,
+ description = "Spring Type Id Header",
+ value = "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
+ ),
+ }
+ )
+ )
+ )
+ @KafkaAsyncOperationBinding
+ public void consume(IncomingPayloadDto payload) {
+ log.info("Received new message: {}", payload.toString());
+
+ processorService.doHandle(payload);
+ }
+
+}
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/outgoing/OutgoingProducer.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/outgoing/OutgoingProducer.java
new file mode 100644
index 0000000000..5630cd9a04
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/adapter/outgoing/OutgoingProducer.java
@@ -0,0 +1,47 @@
+package com.baeldung.boot.documentation.springwolf.adapter.outgoing;
+
+import com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+import static org.springframework.kafka.support.mapping.AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME;
+
+@AllArgsConstructor
+@Component
+@Slf4j
+public class OutgoingProducer {
+
+ private static final String TOPIC_NAME = "outgoing-topic";
+
+ private final KafkaTemplate kafkaTemplate;
+
+ @AsyncPublisher(
+ operation = @AsyncOperation(
+ channelName = TOPIC_NAME,
+ description = "More details for the outgoing topic",
+ headers = @AsyncOperation.Headers(
+ schemaName = "SpringKafkaDefaultHeadersOutgoingPayloadDto",
+ values = {
+ // this header is generated by Spring by default
+ @AsyncOperation.Headers.Header(
+ name = DEFAULT_CLASSID_FIELD_NAME,
+ description = "Spring Type Id Header",
+ value = "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
+ ),
+ }
+ )
+ )
+ )
+ @KafkaAsyncOperationBinding
+ public void publish(OutgoingPayloadDto payload) {
+ log.info("Publishing new message: {}", payload.toString());
+
+ kafkaTemplate.send(TOPIC_NAME, payload);
+ }
+
+}
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/IncomingPayloadDto.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/IncomingPayloadDto.java
new file mode 100644
index 0000000000..a547d55db7
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/IncomingPayloadDto.java
@@ -0,0 +1,25 @@
+package com.baeldung.boot.documentation.springwolf.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Data
+@Schema(description = "Incoming payload model")
+public class IncomingPayloadDto {
+ @Schema(description = "Some string field", example = "some string value", requiredMode = REQUIRED)
+ private String someString;
+
+ @Schema(description = "Some long field", example = "5", requiredMode = NOT_REQUIRED)
+ private long someLong;
+
+ @Schema(description = "Some enum field", example = "FOO2", requiredMode = REQUIRED)
+ private IncomingPayloadEnum someEnum;
+
+ public enum IncomingPayloadEnum {
+ FOO1, FOO2, FOO3
+ }
+
+}
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/OutgoingPayloadDto.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/OutgoingPayloadDto.java
new file mode 100644
index 0000000000..2fb1ab1647
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/dto/OutgoingPayloadDto.java
@@ -0,0 +1,20 @@
+package com.baeldung.boot.documentation.springwolf.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Data
+@Builder
+@Schema(description = "Outgoing payload model")
+public class OutgoingPayloadDto {
+
+ @Schema(description = "Foo field", example = "bar", requiredMode = NOT_REQUIRED)
+ private String foo;
+
+ @Schema(description = "IncomingPayload field", requiredMode = REQUIRED)
+ private IncomingPayloadDto incomingWrapped;
+}
diff --git a/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/service/ProcessorService.java b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/service/ProcessorService.java
new file mode 100644
index 0000000000..980f978f4e
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/java/com/baeldung/boot/documentation/springwolf/service/ProcessorService.java
@@ -0,0 +1,23 @@
+package com.baeldung.boot.documentation.springwolf.service;
+
+import com.baeldung.boot.documentation.springwolf.adapter.outgoing.OutgoingProducer;
+import com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto;
+import com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ProcessorService {
+
+ private final OutgoingProducer outgoingProducer;
+
+ public void doHandle(IncomingPayloadDto payload) {
+ OutgoingPayloadDto message = OutgoingPayloadDto.builder()
+ .foo("Foo message")
+ .incomingWrapped(payload)
+ .build();
+
+ outgoingProducer.publish(message);
+ }
+}
diff --git a/spring-boot-documentation/springwolf/src/main/resources/application.properties b/spring-boot-documentation/springwolf/src/main/resources/application.properties
new file mode 100644
index 0000000000..74cb93499e
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/main/resources/application.properties
@@ -0,0 +1,30 @@
+#########
+# Spring Configuration
+spring.application.name=Baeldung Tutorial Springwolf Application
+
+#########
+# Spring Kafka Configuration
+spring.kafka.bootstrap-servers=localhost:9092
+spring.kafka.consumer.group-id=baeldung-kafka-group-id
+spring.kafka.consumer.properties.spring.json.trusted.packages=com.baeldung.boot.documentation.springwolf.*
+spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
+spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
+spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
+spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
+
+#########
+# Springwolf Configuration
+springwolf.docket.base-package=com.baeldung.boot.documentation.springwolf.adapter
+springwolf.docket.info.title=${spring.application.name}
+springwolf.docket.info.version=1.0.0
+springwolf.docket.info.description=Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf
+
+# Springwolf Kafka Configuration
+springwolf.docket.servers.kafka.protocol=kafka
+springwolf.docket.servers.kafka.url=localhost:9092
+
+springwolf.plugin.kafka.publishing.enabled=true
+springwolf.plugin.kafka.publishing.producer.bootstrap-servers=localhost:9092
+springwolf.plugin.kafka.publishing.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
+springwolf.plugin.kafka.publishing.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
+springwolf.plugin.kafka.publishing.producer.properties.spring.json.add.type.headers=false
diff --git a/spring-boot-documentation/springwolf/src/test/java/com/baeldung/boot/documentation/springwolf/ApiIntegrationTest.java b/spring-boot-documentation/springwolf/src/test/java/com/baeldung/boot/documentation/springwolf/ApiIntegrationTest.java
new file mode 100644
index 0000000000..ac237c0da9
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/test/java/com/baeldung/boot/documentation/springwolf/ApiIntegrationTest.java
@@ -0,0 +1,41 @@
+package com.baeldung.boot.documentation.springwolf;
+
+import org.json.JSONException;
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.skyscreamer.jsonassert.JSONCompareMode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+import org.springframework.test.annotation.DirtiesContext;
+import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@SpringBootTest(classes = {SpringwolfApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@EmbeddedKafka(
+ partitions = 1, brokerProperties = {
+ "listeners=PLAINTEXT://localhost:9092",
+ "port=9092",
+})
+@DirtiesContext
+public class ApiIntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ public void asyncApiResourceArtifactTest() throws JSONException, IOException {
+ // given
+ InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
+ String expected = IOUtils.toString(s, StandardCharsets.UTF_8);
+
+ String url = "/springwolf/docs";
+ String actual = restTemplate.getForObject(url, String.class);
+
+ JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
+ }
+}
diff --git a/spring-boot-documentation/springwolf/src/test/resources/asyncapi.json b/spring-boot-documentation/springwolf/src/test/resources/asyncapi.json
new file mode 100644
index 0000000000..0198733c1c
--- /dev/null
+++ b/spring-boot-documentation/springwolf/src/test/resources/asyncapi.json
@@ -0,0 +1,168 @@
+{
+ "asyncapi": "2.6.0",
+ "info": {
+ "title": "Baeldung Tutorial Springwolf Application",
+ "version": "1.0.0",
+ "description": "Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf"
+ },
+ "defaultContentType": "application/json",
+ "servers": {
+ "kafka": {
+ "url": "localhost:9092",
+ "protocol": "kafka"
+ }
+ },
+ "channels": {
+ "incoming-topic": {
+ "publish": {
+ "operationId": "incoming-topic_publish",
+ "description": "More details for the incoming topic",
+ "bindings": {
+ "kafka": { }
+ },
+ "message": {
+ "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
+ "name": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto",
+ "title": "IncomingPayloadDto",
+ "description": "Incoming payload model",
+ "payload": {
+ "$ref": "#/components/schemas/IncomingPayloadDto"
+ },
+ "headers": {
+ "$ref": "#/components/schemas/SpringKafkaDefaultHeadersIncomingPayloadDto"
+ },
+ "bindings": {
+ "kafka": { }
+ }
+ }
+ }
+ },
+ "outgoing-topic": {
+ "subscribe": {
+ "operationId": "outgoing-topic_subscribe",
+ "description": "More details for the outgoing topic",
+ "bindings": {
+ "kafka": { }
+ },
+ "message": {
+ "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
+ "name": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto",
+ "title": "OutgoingPayloadDto",
+ "description": "Outgoing payload model",
+ "payload": {
+ "$ref": "#/components/schemas/OutgoingPayloadDto"
+ },
+ "headers": {
+ "$ref": "#/components/schemas/SpringKafkaDefaultHeadersOutgoingPayloadDto"
+ },
+ "bindings": {
+ "kafka": { }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HeadersNotDocumented": {
+ "type": "object",
+ "properties": { },
+ "example": { }
+ },
+ "IncomingPayloadDto": {
+ "required": [
+ "someEnum",
+ "someString"
+ ],
+ "type": "object",
+ "properties": {
+ "someEnum": {
+ "type": "string",
+ "description": "Some enum field",
+ "example": "FOO2",
+ "enum": [
+ "FOO1",
+ "FOO2",
+ "FOO3"
+ ]
+ },
+ "someLong": {
+ "type": "integer",
+ "description": "Some long field",
+ "format": "int64",
+ "example": 5
+ },
+ "someString": {
+ "type": "string",
+ "description": "Some string field",
+ "example": "some string value"
+ }
+ },
+ "description": "Incoming payload model",
+ "example": {
+ "someEnum": "FOO2",
+ "someLong": 5,
+ "someString": "some string value"
+ }
+ },
+ "OutgoingPayloadDto": {
+ "required": [
+ "incomingWrapped"
+ ],
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "string",
+ "description": "Foo field",
+ "example": "bar"
+ },
+ "incomingWrapped": {
+ "$ref": "#/components/schemas/IncomingPayloadDto"
+ }
+ },
+ "description": "Outgoing payload model",
+ "example": {
+ "foo": "bar",
+ "incomingWrapped": {
+ "someEnum": "FOO2",
+ "someLong": 5,
+ "someString": "some string value"
+ }
+ }
+ },
+ "SpringKafkaDefaultHeadersIncomingPayloadDto": {
+ "type": "object",
+ "properties": {
+ "__TypeId__": {
+ "type": "string",
+ "description": "Spring Type Id Header",
+ "example": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto",
+ "enum": [
+ "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
+ ]
+ }
+ },
+ "example": {
+ "__TypeId__": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
+ }
+ },
+ "SpringKafkaDefaultHeadersOutgoingPayloadDto": {
+ "type": "object",
+ "properties": {
+ "__TypeId__": {
+ "type": "string",
+ "description": "Spring Type Id Header",
+ "example": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto",
+ "enum": [
+ "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
+ ]
+ }
+ },
+ "example": {
+ "__TypeId__": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
+ }
+ }
+ }
+ },
+ "tags": [ ]
+}