Spring Cloud Stream with Kafka
Overview
This sample project demonstrates how to buildĀ real-time streamingĀ applications usingĀ event-driven architecture,Ā Spring Boot,Spring Cloud Stream,Ā Apache KafkaĀ andĀ Lombok.
By the end of this tutorial youāll have a simple Spring Boot based Greetings micro-service running that
- takes a message from a REST api
- writes it to a Kafka topic
- reads it from the topic
- outputs it to the console
Letās get started!
Btw, youāll find the source code here.
What is Spring Cloud Streaming?
Spring Cloud Stream is a framework built upon Spring Boot for building message-driven microservices.
What is Kafka?
Kafka is a popular high performant and horizontally scalable messaging platform originally developed by LinkedIn.
Installing Kafka
Download Kafka fromĀ hereĀ and untar it:
>Ā tar -xzf kafka_2.11-1.0.0.tgz > cd kafka_2.11-1.0.0
Start Zookeeper and Kafka
On Windows:
>Ā bin\windows\zookeeper-server-start.bat config\zookeeper.properties > bin\windows\kafka-server-start.bat config\server.properties
On Linux or Mac:
>Ā bin/zookeeper-server-start.sh config/zookeeper.properties > bin/kafka-server-start.sh config/server.properties
If Kafka is not running and fails to start after your computer wakes up from hibernation, delete theĀ <TMP_DIR>/kafka-logsfolder and then start Kafka again.
What is Lombok?
Lombok is a java framework that automatically generates getters, setters, toString(), builders, loggers, etc. in the code.
Maven dependencies
Go toĀ https://start.spring.ioĀ to create a maven project:
- Add necessary dependencies:Ā
Spring Cloud Stream,ĀKafka,ĀDevtoolsĀ (for hot redeploys during development, optional),ĀActuatorĀ (for monitoring application, optional),ĀLombokĀ (make sure to also have the Lombok plugin installed in your IDE) - Click the Generate Project button to download the project as a zip file
- Extract zip file and import the maven project to your favourite IDE
Notice the maven dependencies in theĀ pom.xmlĀ file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<!-- Also install the Lombok plugin in your IDE -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- hot reload - press Ctrl+F9 in IntelliJ after a code change while application is running -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>⦠also the <dependencyManagement> section:
<dependencyManagement>
Ā <dependencies>
Ā Ā <dependency>
Ā Ā Ā <!-- Import dependency management from Spring Boot -->
Ā Ā Ā <groupId>org.springframework.boot</groupId>
Ā Ā Ā <artifactId>spring-boot-dependencies</artifactId>
Ā Ā Ā <version>${spring-boot.version}</version>
Ā Ā Ā <type>pom</type>
Ā Ā Ā <scope>import</scope>
Ā Ā </dependency>
Ā Ā <dependency>
Ā Ā Ā <groupId>org.springframework.cloud</groupId>
Ā Ā Ā <artifactId>spring-cloud-stream-dependencies</artifactId>
Ā Ā Ā <version>${spring-cloud-stream.version}</version>
Ā Ā Ā <type>pom</type>
Ā Ā Ā <scope>import</scope>
Ā Ā </dependency>
Ā </dependencies>
</dependencyManagement>⦠and the <repository> section:
<repository> Ā <id>spring-milestones</id> Ā <name>Spring Milestones</name> Ā <url>http://repo.spring.io/libs-milestone</url> Ā <snapshots> Ā Ā <enabled>false</enabled> Ā </snapshots> </repository>
Define the Kafka streams
package com.kaviddiss.streamkafka.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;Ā
public interface GreetingsStreams {
String INPUT = "greetings-in";
String OUTPUT = "greetings-out";
@Input(INPUT)
SubscribableChannel inboundGreetings();
@Output(OUTPUT)
MessageChannel outboundGreetings();
}In order for our application to be able to communicate with Kafka, weāll need to define an outbound stream to write messages to a Kafka topic, and an inbound stream to read messages from a Kafka topic.
Spring Cloud provides a convenient way to do this by simply creating an interface that defines a separate method for each stream.
TheĀ inboundGreetings()Ā method defines the inbound stream to read from Kafka andĀ outboundGreetings()Ā method defines the outbound stream to write to Kafka.
During runtime Spring will create a java proxy based implementation of theĀ GreetingsStreamsĀ interface that can be injected as a Spring Bean anywhere in the code to access our two streams.
Configure Spring Cloud Stream
Our next step is to configure Spring Cloud Stream to bind to our streams in theĀ GreetingsStreamsĀ interface. This can be done by creating aĀ @ConfigurationĀ classĀ com.kaviddiss.streamkafka.config.StreamsConfigĀ with below code:
package com.kaviddiss.streamkafka.config;
import com.kaviddiss.streamkafka.stream.GreetingsStreams;
import org.springframework.cloud.stream.annotation.EnableBinding;
@EnableBinding(GreetingsStreams.class)
public class StreamsConfig {
}Binding the streams is done using theĀ @EnableBindingĀ annotation where theĀ GreatingsServiceĀ interface is passed to.
Configuration properties for Kafka
By default, the configuration properties are stored in theĀ src/main/resources/application.propertiesĀ file.
However I prefer to use the YAML format as itās less verbose and allows to keep both common and environment-specific properties in the same file.
For now, letās renameĀ application.propertiesĀ toĀ application.yamlĀ and paste below config snippet into the file:
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
bindings:
greetings-in:
destination: greetings
contentType: application/json
greetings-out:
destination: greetings
contentType: application/jsonThe above configuration properties configure the address of the Kafka server to connect to, and the Kafka topic we use for both the inbound and outbound streams in our code. They both must use the same Kafka topic!
TheĀ contentTypeĀ properties tell Spring Cloud Stream to send/receive our message objects asĀ Strings in the streams.
Create the message object
Create a simpleĀ com.kaviddiss.streamkafka.model.GreetingsĀ class with below code that will represent the message object we read from and write to theĀ greetingsĀ Kafka topic:
package com.kaviddiss.streamkafka.model;
// lombok autogenerates getters, setters, toString() and a builder (see https://projectlombok.org/):
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter @Setter @ToString @Builder
public class Greetings {
private long timestamp;
private String message;
}Notice how the class doesnāt have any getters and setters thanks to the Lombok annotations. TheĀ @ToStringĀ will generate aĀ toString()Ā method using the classā fields and theĀ @BuilderĀ annotation will allow us creatingĀ GreetingsĀ objects using fluent builder (see below).
Create service layer to write to Kafka
Letās create theĀ com.kaviddiss.streamkafka.service.GreetingsServiceĀ class with below code that will write aĀ Greetingsobject to theĀ greetingsĀ Kafka topic:
package com.kaviddiss.streamkafka.service;
import com.kaviddiss.streamkafka.model.Greetings;
import com.kaviddiss.streamkafka.stream.GreetingsStreams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;
@Service
@Slf4j
public class GreetingsService {
private final GreetingsStreams greetingsStreams;
public GreetingsService(GreetingsStreams greetingsStreams) {
this.greetingsStreams = greetingsStreams;
}
public void sendGreeting(final Greetings greetings) {
log.info("Sending greetings {}", greetings);
MessageChannel messageChannel = greetingsStreams.outboundGreetings();
messageChannel.send(MessageBuilder
.withPayload(greetings)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build());
}TheĀ @ServiceĀ annotation will configure this class as a Spring Bean and inject theĀ GreetingsServiceĀ dependency via the constructor.
TheĀ @Slf4jĀ annotation will generate an SLF4J logger field that we can use for logging.
In theĀ sendGreeting()Ā method we use the injectedĀ GreetingsStreamĀ object to send a message represented by theĀ GreetingsĀ object.
Create REST api
Now weāll be creating a REST api endpoint that will trigger sending a message to Kafka using theĀ GreetingsServiceĀ Spring Bean:
package com.kaviddiss.streamkafka.web;
import com.kaviddiss.streamkafka.model.Greetings;
import com.kaviddiss.streamkafka.service.GreetingsService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;Ā
@RestController
public class GreetingsController {
private final GreetingsService greetingsService;
public GreetingsController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
@GetMapping("/greetings")
@ResponseStatus(HttpStatus.ACCEPTED)
public void greetings(@RequestParam("message") String message) {
Greetings greetings = Greetings.builder()
.message(message)
.timestamp(System.currentTimeMillis())
.build();
greetingsService.sendGreeting(greetings);
}
}TheĀ @RestControllerĀ annotation tells Spring that this is a Controller bean (the C from MVC). TheĀ greetings()Ā method defines anĀ HTTP GET /greetingsĀ endpoint that takes aĀ messageĀ request param and passes it to theĀ sendGreeting()Ā method inĀ GreetingsService.
Listening on the greetings Kafka topic
Letās create aĀ com.kaviddiss.streamkafka.service.GreetingsListenerĀ class that will listen to messages on theĀ greetingsKafka topic and log them on the console:
package com.kaviddiss.streamkafka.service;
import com.kaviddiss.streamkafka.model.Greetings;
import com.kaviddiss.streamkafka.stream.GreetingsStreams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class GreetingsListener {
@StreamListener(GreetingsStreams.INPUT)
public void handleGreetings(@Payload Greetings greetings) {
log.info("Received greetings: {}", greetings);
}
}TheĀ @ComponentĀ annotation similarly toĀ @ServiceĀ andĀ @RestControllerĀ defines a Spring Bean.
GreetingsListenerĀ has a single method,Ā handleGreetings()Ā that will be invoked by Spring Cloud Stream with every newĀ GreetingsĀ message object on theĀ greetingsĀ Kafka topic. This is thanks to theĀ @StreamListenerĀ annotation configured for theĀ handleGreetings()Ā method.
Running the application
The last piece of the puzzle is theĀ com.kaviddiss.streamkafka.StreamKafkaApplicationĀ class that was auto-generated by the Spring Initializer:
package com.kaviddiss.streamkafka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamKafkaApplication {
public static void main(String[] args) {
SpringApplication.run(StreamKafkaApplication.class, args);
}
}No need to make any changes here. You can either run this class as a Java application from your IDE, or run the application from the command line using the Spring Boot maven plugin:
>Ā mvn spring-boot:run
Once the application is running, go toĀ http://localhost:8080/greetings?message=helloĀ in the browser and check your console.
Summary
I hope you enjoyed this tutorial. Feel free to ask any questions and leave your feedback.
| Published on Java Code Geeks with permission by David Kiss, partner at our JCG program. See the original article here: Spring Cloud Stream with Kafka Opinions expressed by Java Code Geeks contributors are their own. |





