Kafka Streams
Spring Actuator
Health Check Endpoint
Stream Status
Application Monitoring

Spring Actuator + Kafka Streams - Add kafka stream status to health check endpoint

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To surface Kafka Streams state in Spring Boot Actuator, expose a health contributor that reads the current stream state and maps it to an Actuator status. The main design question is not how to call the API, but how to decide which Kafka Streams states should count as healthy, degraded, or down for your deployment model.

Where to read the Kafka Streams state

If your application manages KafkaStreams directly, you can inject that instance into a HealthIndicator. In Spring-managed Kafka Streams applications, another common option is to obtain the runtime instance from StreamsBuilderFactoryBean, which is the bean Spring Kafka uses to create and start the streams client.

A basic health indicator looks like this:

java
1package com.example.health;
2
3import org.apache.kafka.streams.KafkaStreams;
4import org.springframework.boot.actuate.health.Health;
5import org.springframework.boot.actuate.health.HealthIndicator;
6import org.springframework.stereotype.Component;
7
8@Component
9public class KafkaStreamsHealthIndicator implements HealthIndicator {
10
11    private final KafkaStreams kafkaStreams;
12
13    public KafkaStreamsHealthIndicator(KafkaStreams kafkaStreams) {
14        this.kafkaStreams = kafkaStreams;
15    }
16
17    @Override
18    public Health health() {
19        KafkaStreams.State state = kafkaStreams.state();
20
21        return switch (state) {
22            case RUNNING, REBALANCING -> Health.up()
23                    .withDetail("kafkaStreamsState", state.name())
24                    .build();
25            case CREATED, PENDING_SHUTDOWN, PENDING_ERROR -> Health.status("OUT_OF_SERVICE")
26                    .withDetail("kafkaStreamsState", state.name())
27                    .build();
28            case NOT_RUNNING, ERROR -> Health.down()
29                    .withDetail("kafkaStreamsState", state.name())
30                    .build();
31        };
32    }
33}

The state mapping is the important part. RUNNING is healthy. REBALANCING is often still healthy enough for liveness, but it may not be ready for traffic if you use the health check to gate readiness.

Using StreamsBuilderFactoryBean in Spring Kafka

When Spring creates the streams client for you, there may not be a directly injectable KafkaStreams bean at startup time. In that case, build the health indicator around StreamsBuilderFactoryBean and handle the case where the runtime instance has not been started yet.

java
1package com.example.health;
2
3import org.apache.kafka.streams.KafkaStreams;
4import org.springframework.boot.actuate.health.Health;
5import org.springframework.boot.actuate.health.HealthIndicator;
6import org.springframework.kafka.config.StreamsBuilderFactoryBean;
7import org.springframework.stereotype.Component;
8
9@Component
10public class KafkaStreamsFactoryBeanHealthIndicator implements HealthIndicator {
11
12    private final StreamsBuilderFactoryBean factoryBean;
13
14    public KafkaStreamsFactoryBeanHealthIndicator(StreamsBuilderFactoryBean factoryBean) {
15        this.factoryBean = factoryBean;
16    }
17
18    @Override
19    public Health health() {
20        KafkaStreams kafkaStreams = factoryBean.getKafkaStreams();
21
22        if (kafkaStreams == null) {
23            return Health.status("OUT_OF_SERVICE")
24                    .withDetail("reason", "KafkaStreams has not been initialized yet")
25                    .build();
26        }
27
28        KafkaStreams.State state = kafkaStreams.state();
29        return Health.up().withDetail("kafkaStreamsState", state.name()).build();
30    }
31}

That example keeps the wiring realistic for Spring Kafka applications and prevents a null failure during application startup.

Exposing the health endpoint cleanly

Actuator still has to expose the health endpoint and, if you want to see the details, it has to be configured to show them.

yaml
1management:
2  endpoints:
3    web:
4      exposure:
5        include: health,info
6  endpoint:
7    health:
8      show-details: always
9      probes:
10        enabled: true

If you use Kubernetes, you can keep the same health contributor and then decide whether it belongs in the readiness group, the liveness group, or only the general health endpoint. Readiness is the usual choice because a stream app that is rebalancing may still be alive even if it should not yet receive traffic.

Choosing a status policy that matches operations

Do not blindly map every non-RUNNING state to DOWN. That often creates noisy alerts during normal rebalances and rolling deploys. A better approach is to separate operational states into three categories:

  • healthy enough to serve: RUNNING
  • alive but temporarily unstable: REBALANCING, sometimes CREATED
  • failed or stopped: ERROR, NOT_RUNNING

If the health endpoint feeds a dashboard, you may keep REBALANCING as UP and rely on the details field. If it feeds readiness checks, OUT_OF_SERVICE is often a better signal because it removes the pod from service without triggering a restart loop.

Spring Cloud Stream binder note

If the application uses the Spring Cloud Stream Kafka Streams binder instead of managing the streams runtime directly, first check what binder health information is already available. In that setup, Actuator integration may already include binder-specific health contributors, and a custom indicator may only be necessary if you want a different status policy or more detailed metadata.

That distinction matters because the correct extension point depends on which Spring stack is actually starting and owning the Kafka Streams instance.

Common Pitfalls

A common mistake is injecting KafkaStreams directly even though the application lifecycle is managed through StreamsBuilderFactoryBean, which can leave you with null or timing issues.

Another mistake is treating REBALANCING as a hard failure. That usually produces false alarms during healthy cluster activity.

A third mistake is forgetting Actuator exposure settings, then assuming the health indicator is broken because the endpoint does not show any details.

Summary

  • Expose Kafka Streams state through a custom HealthIndicator or HealthContributor.
  • Read the runtime from KafkaStreams directly only when your application really owns that instance.
  • Use StreamsBuilderFactoryBean in Spring Kafka applications that let Spring create the streams client.
  • Map Kafka Streams states to health statuses based on how the endpoint is used operationally.
  • Expose health details and place the contributor in the right readiness or liveness context.

Course illustration
Course illustration

All Rights Reserved.