Building Reactive Microservices With Spring WebFlux on Kubernetes
Spring WebFlux on Kubernetes delivers high-throughput, low-latency I/O with fewer resources using reactive IO, Resilience4j, and metrics-based autoscaling.
Join the DZone community and get the full member experience.
Join For FreeMigrating from a monolithic Java 8 system to a reactive microservice architecture on Kubernetes allowed us to dramatically improve performance and maintainability. In this article, I’ll share our journey, key Spring Cloud Kubernetes features we adopted, the challenges we faced during development, and the lessons we learned along the way.
Business Logic
We have a data processing logic that streams information into S3 storage using Kafka, Spark Streaming, and Iceberg. Initially, we encountered multiple challenges, such as file optimization issues and Spark’s unpredictable memory behavior. After addressing these issues, we achieved significant cost savings. Once the insert service was completed, we needed to select an appropriate search engine service. We chose Trino as it fulfilled the needs of our data science department. We also serve customers who perform operations on our S3 data, which can result in high system load. Before this modernization, our platform ran on an old monolithic architecture built with Java 8, which created several performance and maintenance challenges.
Old Monolith Architecture
Our monolithic architecture was an example of a legacy design that lacked scalability and flexibility. It was a mix of MapReduce jobs, insert services connected to various databases, search services, and schema services. When one component experienced issues, the entire system — and therefore our customers — were affected. Small code fixes could take a significant amount of time due to the tightly coupled structure. Additionally, most of the libraries were deprecated, and updating them caused compatibility issues across the stack. We were limited by Java 8’s older garbage collection and incompatibility with modern libraries. We also relied on Zookeeper to store global parameters, which was cumbersome to maintain and added operational overhead.
New Reactive Architecture
We decided to extract the search service and rebuild it using reactive Spring technology on modern Java, deploying it via Spring Cloud Kubernetes. Spring Cloud Kubernetes provides Spring Cloud interface implementations that consume native Kubernetes services. After exploring its capabilities, we implemented several key features described below.
Key Spring Cloud Kubernetes Features
A. ConfigMap Property Source
Kubernetes provides a resource called ConfigMap to externalize configuration parameters in the form of key-value pairs or embedded application.properties or application.yaml files. The Spring Cloud Kubernetes Config project makes these ConfigMap instances available at application startup and triggers hot reloading of beans or the Spring context when changes are detected. This allows dynamic configuration without restarts.
Example configuration:
spring:
application:
name: cloud-k8s-app
cloud:
kubernetes:
config:
name: default-name
namespace: default-namespace
sources:
- name: c1
- namespace: n2
- namespace: n3
name: c3
B. Secrets Property Source
Kubernetes also supports Secrets for storing sensitive information such as passwords, OAuth tokens, and API keys. We integrated these using Spring Cloud Kubernetes Secrets configuration.
Example configuration:
cloud:
kubernetes:
secrets:
enabled: true
fail-fast: true
sources:
- name: Secret
namespaces: hades-h3
C. Leader Election
Spring Cloud Kubernetes includes a leader election mechanism that implements Spring Integration’s leader election API using a Kubernetes ConfigMap. The following configuration enables leader election.
leader:
enabled: true
When a pod becomes the leader, the following code executes:
@EventListener()
public void foo(OnGrantedEvent event) {
if (event.getContext() != null) {
logger.info(new LogEvent("I am foo leader."));
executeCreateView();
} else {
logger.info(new LogEvent("Skipping foo because I'm not the leader."));
}
}
The method executeCreateView() creates a view every 30 seconds in our Trino search engine. We implemented the leader mechanism because only one instance should perform this operation, preventing race conditions among multiple pods.
D. Spring Cloud Kubernetes Configuration Watcher
Kubernetes allows mounting ConfigMaps or Secrets as volumes inside containers. When their contents change, the mounted volume updates automatically. We developed a GlobalConfig class responsible for reading configuration values from the Kubernetes ConfigMap. We used the @RefreshScope annotation so that any change in configuration automatically triggers a bean reload, allowing us to apply updates without restarting the application.
E. Spring Boot Actuator
Spring Boot Actuator provides production-ready features for monitoring, metrics collection, and health checks without requiring custom implementation. We integrated it to monitor application performance, analyze traffic, and track database states. We also used these metrics to build Grafana dashboards for better visibility.

Problems During Development
During testing, we observed an issue where CPU usage increased gradually over time. After a detailed investigation, we discovered that the Actuator was collecting metrics for every external IP calling the service, which caused a memory and CPU increase. We resolved this by using MeterFilter.deny() to filter out unnecessary metrics and retain only what we needed.
We also faced the challenge of designing a single class that could read configuration data from both the Kubernetes ConfigMap and local application resources. We implemented it as follows:
@ConfigurationProperties(prefix = "global")
@RefreshScope
@Configuration
public class GlobalConfig extends GitLabPush {
private String config;
public void setConfig(String config) {
this.config = config;
}
}
The prefix “global” defines the upper level of the ConfigMap. When scanning, the field “config” is retrieved from the map with its associated values. Example ConfigMap entry:
global.config: {"field": "data"}
Another challenge arose when updating the Kubernetes ConfigMap via our API. We needed to synchronize these changes with our Argo projects, where YAMLs were stored. To ensure consistency, we added a pushToGit() function that used the GitLab API to automatically commit updates to the Git repository.
Lessons Learned
Refactoring our monolithic service into microservices provided a far more efficient development process, improved performance, and granted access to the full range of features in Spring Cloud Kubernetes. We gained scalability, dynamic configuration management, and simplified observability. This migration allowed our teams to deploy updates faster, recover from issues quickly, and reduce maintenance overhead.
Conclusion
Transitioning from a monolithic Java 8 architecture to a reactive microservice environment built with Spring WebFlux and Kubernetes transformed the reliability and agility of our system. The combination of ConfigMaps, Secrets, Leader Election, and Actuator monitoring enabled us to create a flexible, cloud-native platform ready to handle complex workloads at scale.
Opinions expressed by DZone contributors are their own.
Comments