eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

Documenting APIs is an essential part of building applications. It’s a shared contract that we have with our clients. Moreover, it documents in detail how our integration points work. The documentation should be easy to access, understand and implement.

In this tutorial, we’ll look at Springwolf for documenting event-driven Spring Boot services. Springwolf implements the AsyncAPI specification, an adaption of the OpenAPI specification for event-driven APIs. Springwolf is protocol-agnostic and covers the Spring Kafka, RabbitMQ, JMS, AWS SNS & SQS, STOMP (WebSocket), and CloudStream implementations.

Using Spring Kafka as our event-driven system, Springwolf generates an AsyncAPI document from code for us. Some consumers are auto-detected. Other information is provided by us.

2. Setting up Springwolf

To get started with Springwolf, we add the dependency and configure it.

2.1. Adding the Dependency

Assuming we have a running Spring application with Spring Kafka, we add springwolf-kafka to our Maven project as a dependency in the pom.xml file:

<dependency>
    <groupId>io.github.springwolf</groupId>
    <artifactId>springwolf-kafka</artifactId>
    <version>1.7.0</version>
</dependency>

The latest version can be found on Maven Central, and support for other bindings besides Spring Kafka is mentioned on the project’s website.

2.2. application.properties Configuration

In the most basic form, we add the following Springwolf configuration to our application.properties:

# 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-server.protocol=kafka
springwolf.docket.servers.kafka-server.host=${spring.kafka.bootstrap-servers}

The first block sets the general Springwolf configuration. This includes the base-package, which is used by Springwolf for the auto-detection of listeners. Also, we set general information under the docket configuration key, which appears in the AsyncAPI document.

Then, we set springwolf-kafka specific configuration. Again, this appears in the AsyncAPI document.

2.3. Verification

Now, we are ready to run our application. After the application has started, the AsyncAPI document is available at the /springwolf/docs path by default:

http://localhost:8080/springwolf/docs

3. The AsyncAPI Document

The AsyncAPI document follows a similar structure as the OpenAPI document. First, we look at the key sections only. The specification is available on the AsyncAPI website. For brevity, we’ll only look at a subset of properties.

In the following subsections, we’ll look at the AsyncAPI document in JSON format in incremental steps. We start with the following structure:

{
    "asyncapi": "3.0.0",
    "info": { ... },
    "servers": { ... },
    "channels": { ... },
    "components": {
        "messages": { ... },
        "schemas": { ... }
    },
    "operations": { ... }
}

3.1. info Section

The info section of the document contains information about the application itself. This includes at least the following fields: title, application version, and description.

Based on the information we added to the configuration, the following structure is created:

"info": {
    "title": "Baeldung Tutorial Springwolf Application",
    "version": "1.0.0",
    "description": "Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf"
}

3.2. servers Section

Similarly, the servers section contains information about our Kafka broker and is based on the application.properties configuration above:

"servers": {
    "kafka-server": {
        "host": "127.0.0.1:9092",
        "protocol": "kafka"
    }
}

3.3. channels Section

This section is empty at this point because we didn’t configure any consumers or producers in our application yet. After configuring them in a later section, we’ll see the following structure:

"channels": {
    "incoming-topic": {
         "address": "incoming-topic",
         "messages": {
            "IncomingPayloadDto": {
                "$ref": "#/components/messages/IncomingPayloadDto"
            }
        }
    }
}

The generic term channel refers to topic in Kafka terminology.

To avoid duplicating identical payload information in multiple channels and operations, AsyncAPI uses the $ref notation to indicate a reference in the components section of the AsyncAPI document.

3.4. components Section

Again, this section is empty at this point but will have the following structure:

"components": {
    "messages": {
        "IncomingPayloadDto": {
            ...
            "payload": {
                "schema": {
                    "$ref": "#/components/schemas/IncomingPayloadDto"
                }
            },
            "headers": ...
        }
    },
    "schemas": {
        "IncomingPayloadDto": {
            "type": "object",
             "properties": {
                ...
                "someString": {
                    "type": "string"
                }
            },
            "example": {
                "someEnum": "FOO2",
                "someLong": 1,
                "someString": "string"
            }
        }
    }
}

The components section contains all the details of the $ref references, including #/components/messages/IncomingPayloadDto and #/components/schemas/IncomingPayloadDto.

Besides the message, also the payload schema is located in the components section. The schema fields include type of data, properties of the payload and an optional (JSON) example of the payload.

3.5. Operations Section

A single operation object links a channel (3.3) with a message (3.4.) which can either be sent or received. It may contain additional information like a description.

"operations": {
    "incoming-topic_receive_consume": {
        "action": "receive",
        "channel": {
            "$ref": "#/channels/incoming-topic"
        },
        "title": "incoming-topic_receive",
        "description": "More details for the incoming topic",
        "messages": [
            {
                "$ref": "#/channels/incoming-topic/messages/IncomingPayloadDto"
            }
        ]
    }
}

Similar to channels, the operations object is a key-value map of operation id to operation object. In this case, incoming-topic_receive_consume is the Springwolf generated operation id.

4. Documenting Consumers

Springwolf auto-detects all @KafkaListener annotations, which are shown next. Additionally, we use the @AsyncListener annotation to provide more details manually.

4.1. Auto-detection of @KafkaListener Annotation

By using Spring-Kafka’s @KafkaListener annotation on a method, Springwolf finds the consumer within the base-package automatically:

@KafkaListener(topics = TOPIC_NAME)
public void consume(IncomingPayloadDto payload) {
    // ...
}

Now, the AsyncAPI document does contain the channel TOPIC_NAME with the publish operation and IncomingPayloadDto schema as we saw earlier.

4.2. Manually Documenting Consumer via @AsyncListener Annotation

Using auto-detection and @AsyncListener together may lead to duplicates. To be able to add more information manually, we disable the @KafkaListener auto-detection completely and add the following line to the application.properties file:

springwolf.plugin.kafka.scanner.kafka-listener.enabled=false

Next, we add the Springwolf @AsyncListener annotation to the same method and provide additional information for the AsyncAPI document:

@KafkaListener(topics = TOPIC_NAME)
@AsyncListener(
    operation = @AsyncOperation(
        channelName = TOPIC_NAME,
        description = "More details for the incoming topic"
    )
)
@KafkaAsyncOperationBinding
public void consume(IncomingPayloadDto payload) {
    // ...
}

Also, we add the @KafkaAsyncOperationBinding annotation to connect the generic @AsyncOperation annotation with the Kafka broker in the servers section. Kafka protocol-specific information is also set using this annotation.

After the change, the AsyncAPI document contains the updated documentation.

5. Documenting Producers

Producers are documented manually by using the Springwolf @AsyncPublisher annotation.

5.1. Manually Documenting Producers via @AsyncPublisher Annotation

Similar to the @AsyncListener annotation, we add the @AsyncPublisher annotation to the publisher method and add the @KafkaAsyncOperationBinding annotation as well:

@AsyncPublisher(
    operation = @AsyncOperation(
        channelName = TOPIC_NAME,
        description = "More details for the outgoing topic"
    )
)
@KafkaAsyncOperationBinding
public void publish(OutgoingPayloadDto payload) {
    kafkaTemplate.send(TOPIC_NAME, payload);
}

Based on this, Springwolf adds for the TOPIC_NAME channel a subscribe operation to the channels section using the information provided above. The payload type is extracted from the method signature in the same way as it’s done for the @AsyncListener.

6. Enhancing the Documentation

The AsyncAPI specification covers even more features than we have covered above. Next, we document the default Spring Kafka header __TypeId__ and improve the documentation of the payload.

6.1. Adding Kafka Headers

When running a native Spring Kafka application, Spring Kafka automatically adds the header __TypeId__ to assist in the deserialization of the payload in the consumer.

We add the __TypeId__ header to the documentation by setting the headers field on the @AsyncOperation of the @AsyncListener (or @AsyncPublisher) annotation:

@AsyncListener(
    operation = @AsyncOperation(
        ...,
        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"
                ),
            }
        )
    )
)

Now, the AsyncAPI document contains a new field headers as part of the message object.

6.2. Adding Payload Details

We use the Swagger @Schema annotation to provide additional information about the payload. In the following code snippet, we set the description, an example value, and whether the field is required:

@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;
}

Based on this, we see the enriched OutgoingPayloadDto schema in the AsyncAPI document:

"OutgoingPayloadDto": {
    "type": "object",
    "description": "Outgoing payload model",
    "properties": {
        "incomingWrapped": {
            "$ref": "#/components/schemas/IncomingPayloadDto"
        },
        "foo": {
            "type": "string",
            "description": "Foo field",
            "example": "bar"
        }
    },
    "required": [
        "incomingWrapped"
    ],
    "example": {
        "incomingWrapped": {
            "someEnum": "FOO2",
            "someLong": 5,
            "someString": "some string value"
         },
         "foo": "bar"
    }
}

The full AsyncAPI document of our application is available in the linked example project.

7. Verify API Contract Changes

When the AsyncAPI document is checked into the code repository, we can compare it to the AsyncAPI document generated by the current code. This allows to us to verify that (a) the added/migrated event class matches our expected example and (b) a harmless refactoring did indeed leave the API contract unchanged.

A simple test is all we need:

@Test
public void asyncApiArtifactTest() {
    InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
    String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).trim();

    String actual = restTemplate.getForObject("/springwolf/docs", String.class);

    JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
}

8. Using Springwolf UI

Springwolf has its own UI, although any AsyncAPI conforming document renderer can be used.

8.1. Adding the springwolf-ui Dependency

To use springwolf-ui, we add the dependency to our pom.xml, rebuild and restart our application:

<dependency>
    <groupId>io.github.springwolf</groupId> 
    <artifactId>springwolf-ui</artifactId
    <version>1.7.0</version>
</dependency>

8.2. Viewing the AsyncAPI Document

Now, we open the documentation in our browser by visiting http://localhost:8080/springwolf/asyncapi-ui.html.

The webpage has a similar structure compared to the AsyncAPI document and displays information about the application, details about the servers, channels, and schemas:

springwolf landing

8.3. Publishing Messages

Springwolf allows us to publish messages from the browser directly. Clicking the Publish button puts a message on Kafka directly. The headers and message are adjustable as needed:

springwolf channel

Due to security concerns, this feature is disabled by default. To enable publishing, we add the following line to our application.properties file:

springwolf.plugin.kafka.publishing.enabled=true

9. Conclusion

In this article, we have set up Springwolf in an existing Spring Boot Kafka application.

Using the consumer auto-detection, an AsyncAPI conform document is generated automatically. We further enhanced the documentation through manual configuration.

Apart from downloading the AsyncAPI document through the provided REST endpoint, we used springwolf-ui to view the documentation in a browser.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)