- Published on
Brewing Real-time Notifications: A Magician Guide to Spring Boot, Kafka, ReactJS, and Docker
- Authors

- Name
- Gary Huynh
- @gary_atruedev
Why hello there fellow Java enthusiast! Ready to dive into a mad jumble of code and coffee? Excellent! Make sure to don your coding helmets (it's a thing, trust me!) and remember - we're about to wrangle some Kafka and Spring Boot to implement a killer notification feature for your web and mobile clients. So let's get those creative Java juices flowing!
๐ Building Blocks for Success
Before diving in, enhance your real-time skills with these resources:
- ๐๏ธ Microservices Architecture with Kubernetes - Deploy your notification service
- ๐ Change Data Capture with Debezium - Trigger notifications from database events
- ๐ก๏ธ Secure Your Kafka Streams - SSL/TLS for production
- ๐ฏ Spring Boot Exception Handling - Handle errors gracefully
First things first, let's talk about what we're dealing with here: Kafka and Spring Boot.
Kafka is a bit like a super-efficient postman, who's guzzling energy drinks and always on the move. It's a distributed streaming platform that allows apps to publish and subscribe to streams of records. Spring Boot, on the other hand, is like your trusty toolbox - a comfy zone filled with everything you need for application development.
Step 1: Conjure a new Spring Boot Project with Spring Initializr
Spring Initializr is like a magic potion for creating Spring Boot projects. It's the secret recipe of seasoned Java sorcerers!
- Zip on over to Spring Initializr.
- Fill out the
GroupandArtifactdetails to your heart's content. - Choose your dependencies like a pro - for this mission, we'll need
Spring Web,Spring for Apache Kafka, andSpring WebSocket. - Click 'Generate' with a flourish, and poof! Your zipped project is ready for download.
- Once downloaded, unzip it and open it in your IDE. I'm going to use
IntelliJ IDEA, because, well, it's the IDE of champions!
Step 2: The Kafka Potion - Producer and Consumer Configuration
Now that we have our Spring Boot project, let's bring Kafka into the mix.
Kafka Producer Configurationis the wizardry that sends our notifications. Here's the code to whip up this concoction:
@Configuration
public class KafkaProducerConfig {
@Value("${kafka.bootstrapAddress}")
private String bootstrapAddress;
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
- Let's create a
NotificationServicethat uses thisKafka producerto send the notification:
@Service
public class NotificationService {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
public void sendNotification(String message){
kafkaTemplate.send("notifications", message);
}
}
- But who will listen to these messages? Enter
Kafka ConsumerConfiguration:
@Configuration
public class KafkaConsumerConfig {
@Value("${kafka.bootstrapAddress}")
private String bootstrapAddress;
@Value("${kafka.groupId}")
private String groupId;
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(configProps);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
- Now, create a
NotificationConsumerServicethatlistensto the messages and thensendsthem to our clients:
@Service
public class NotificationConsumerService {
@Autowired
private SimpMessagingTemplate template;
@K
afkaListener(topics = "notifications", groupId = "group_id")
public void consume(String message){
template.convertAndSend("/topic/notifications", message);
}
}
Step 3: Brewing the React.js Frontend Potion
This one's for our web clients. Create a new React.js application and add this NotificationComponent to see the notifications on the webpage. It uses a WebSocket to listen to notifications from our server and updates the UI whenever it receives a notification.
import React, { Component } from 'react';
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
class NotificationComponent extends Component {
constructor(props) {
super(props);
this.state = { notifications: [] };
}
componentDidMount() {
this.connect();
}
connect = () => {
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);
stompClient.connect({}, this.onConnected, this.onError);
};
onConnected = () => {
this.stompClient.subscribe('/topic/notifications', this.onMessageReceived);
};
onMessageReceived = (payload) => {
let notification = JSON.parse(payload.body);
this.setState((prevState) => ({
notifications: [...prevState.notifications, notification],
}));
};
onError = (error) => {
console.log("Could not connect you to the server. Please, try again later!");
};
render() {
return (
<div className="notificationComponent">
{this.state.notifications.map((notification, i) => (
<div key={i} className="notification">
{notification}
</div>
))}
</div>
);
}
}
export default NotificationComponent;
Step 4: Bottle Up Your App With Docker
Now that our potions are all brewed, let's bottle them up.
- Create a
Dockerfilein the root of your Spring Boot project:
FROM openjdk:11
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
- Build your
Docker image:
docker build -t my-spring-boot-app .
- Next, create a
Dockerfilefor yourReact.jsapplication:
FROM node:14 as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
- Build this Docker image:
docker build -t my-react-app .
- Now that you have your Docker images, you can use
Docker Composeto start your whole stack:
docker-compose up
And voila! You've just weaved together Spring Boot, Kafka, React.js, and Docker to create a real-time notification feature. Your Java mastery knows no bounds! May the Force of Java be with you, always!
๐ Level Up Your Real-time Game
Next Steps in Your Journey:
- ๐ Implement CDC for Real-time Data Sync - Capture database changes automatically
- ๐๏ธ Scale with Microservices - Deploy to Kubernetes
- ๐ก๏ธ Add Security Layers - Protect your notifications
Enhance Your Implementation:
- ๐ฑ Push Notifications - Extend to mobile devices with FCM/APNS
- ๐พ Message Persistence - Store notifications for offline users
- ๐ Analytics Dashboard - Track delivery and engagement rates
- ๐ Authentication - Secure WebSocket connections with JWT
Troubleshooting Tips:
- ๐ Debug Like a Pro - Handle Kafka errors gracefully
- ๐ Performance Tuning - Optimize Kafka partitions and consumer groups
- ๐งช Test Your APIs - Ensure reliability
Remember: Real-time features can transform user experience, but they require careful architecture and monitoring!