From Monolith to Microservices: Lessons Learned in the Real World
When I started my career as a Java developer, everything revolved around the monolith. We built giant web apps with layered architectures - controller, service, DAO -packed into a single codebase, deployed as one glorious WAR file. It felt structured. Predictable. Safe. And back then, I genuinely thought: This is the best way to build web applications in Java. It had everything -centralised logic, a single database, and the comfort of knowing exactly where every line of code lived. Sure, the app took forever to build and deploy, and debugging meant scrolling through a sea of logs, but hey, that was just the way things were. Then came microservices. At first, it sounded like a buzzword from another tech universe. Small services? Independent deployment? Teams owning their domains? It felt… chaotic. Why would anyone break a perfectly good monolith into a hundred little pieces? Then we heard whispers from teams who had tried them. Then came diagrams, workshops, and finally, the day the manager said: “We’re going to migrate to microservices” 1. Lesson One: It’s not just about splitting code — it’s about changing mindset When we began the migration, we thought the hard part would be breaking the code. But it turns out, the real challenge was breaking the mental model. Breaking the monolith wasn’t just about cutting the code into pieces. We had to rethink how we built the system. To do this, we used Domain Sizing to ensure that each microservice was the right size- big enough to handle its responsibilities but not so large that it became cumbersome. We looked at different areas of the business and grouped them into core, supporting, or generic categories, which helped us define service boundaries. Additionally, we conducted Event Storming sessions, a collaborative and interactive approach where various stakeholders helped identify key business events in the system. This exercise helped us understand how these events were connected and allowed us to group them into meaningful, domain-driven services. Takeaway: Microservices don’t just change your architecture -they transform how you think about building systems. *2. Lesson Two: Microservices will expose your hidden dependencies * The monolith hides a lot of sins. Circular dependencies, tightly coupled modules, and fragile database joins — all tucked neatly under one application context. I remember one service that couldn’t function unless it accessed five different tables across three different domains. That’s when I realised: we weren’t building services — we were just moving spaghetti into smaller bowls. How we overcame it: To resolve this, we revisited the domain boundaries and focused on creating a more cohesive service by making sure it only interacted with the data it truly needed. We started with API-first design, ensuring that each service had well-defined interfaces. We also introduced data ownership within each service, meaning that the services would manage their data rather than sharing it across multiple systems. This helped reduce unnecessary dependencies and allowed each service to function more independently. Takeaway: Microservices are brutally honest. They’ll show you everything that’s wrong — and force you to fix it. *3. Lesson Three: Event-driven architecture is a game-changer * One of the most magical realisations for me was how microservices could talk to each other without needing to call each other directly. Enter: Kafka. We started using events to decouple services. Instead of Service A asking Service B for something, Service B would announce when it was done, and Service A could choose to react. Suddenly, the system felt more alive, less like a tightly scripted play, and more like an ecosystem where services reacted to each other naturally. And that moment when I saw an event flow through Kafka, trigger another service, and complete a flow without a single REST call? Yeah, that was a this is also there?! moment for me. Takeaway: Events make systems more resilient, more scalable, and frankly, more fun to build. *4. Lesson Four: Observability is your oxygen * When our services went live, everything seemed fine until one failed silently. No errors, no logs, just… silence. We had no idea where the request went. That’s when it hit me: In a distributed system, if you can’t see it, you can’t fix it. To tackle this, I was introduced to structured logging to capture important data points in a consistent format, making it easier to track issues. We also set up centralized log management to collect logs from different services in one place for quick analysis. Additionally, we implemented distributed tracing using Zipkin, which allowed us to track requests as they moved through multiple services, helping us pinpoint failures and understand system behaviour more effectively. Lesson? Don’t wait for a production issue to make your system observable. Bake it in from day one. Takeaway: Observability isn’t optional in microservices

When I started my career as a Java developer, everything revolved around the monolith. We built giant web apps with layered architectures - controller, service, DAO -packed into a single codebase, deployed as one glorious WAR file. It felt structured. Predictable. Safe.
And back then, I genuinely thought: This is the best way to build web applications in Java.
It had everything -centralised logic, a single database, and the comfort of knowing exactly where every line of code lived. Sure, the app took forever to build and deploy, and debugging meant scrolling through a sea of logs, but hey, that was just the way things were.
Then came microservices.
At first, it sounded like a buzzword from another tech universe. Small services? Independent deployment? Teams owning their domains? It felt… chaotic. Why would anyone break a perfectly good monolith into a hundred little pieces?
Then we heard whispers from teams who had tried them. Then came diagrams, workshops, and finally, the day the manager said:
“We’re going to migrate to microservices”
1. Lesson One: It’s not just about splitting code — it’s about changing mindset
When we began the migration, we thought the hard part would be breaking the code. But it turns out, the real challenge was breaking the mental model.
Breaking the monolith wasn’t just about cutting the code into pieces. We had to rethink how we built the system. To do this, we used Domain Sizing to ensure that each microservice was the right size- big enough to handle its responsibilities but not so large that it became cumbersome. We looked at different areas of the business and grouped them into core, supporting, or generic categories, which helped us define service boundaries. Additionally, we conducted Event Storming sessions, a collaborative and interactive approach where various stakeholders helped identify key business events in the system. This exercise helped us understand how these events were connected and allowed us to group them into meaningful, domain-driven services.
Takeaway: Microservices don’t just change your architecture -they transform how you think about building systems.
*2. Lesson Two: Microservices will expose your hidden dependencies
*
The monolith hides a lot of sins. Circular dependencies, tightly coupled modules, and fragile database joins — all tucked neatly under one application context.
I remember one service that couldn’t function unless it accessed five different tables across three different domains. That’s when I realised: we weren’t building services — we were just moving spaghetti into smaller bowls.
How we overcame it:
To resolve this, we revisited the domain boundaries and focused on creating a more cohesive service by making sure it only interacted with the data it truly needed. We started with API-first design, ensuring that each service had well-defined interfaces. We also introduced data ownership within each service, meaning that the services would manage their data rather than sharing it across multiple systems. This helped reduce unnecessary dependencies and allowed each service to function more independently.
Takeaway: Microservices are brutally honest. They’ll show you everything that’s wrong — and force you to fix it.
*3. Lesson Three: Event-driven architecture is a game-changer
*
One of the most magical realisations for me was how microservices could talk to each other without needing to call each other directly.
Enter: Kafka.
We started using events to decouple services. Instead of Service A asking Service B for something, Service B would announce when it was done, and Service A could choose to react.
Suddenly, the system felt more alive, less like a tightly scripted play, and more like an ecosystem where services reacted to each other naturally. And that moment when I saw an event flow through Kafka, trigger another service, and complete a flow without a single REST call?
Yeah, that was a this is also there?! moment for me.
Takeaway: Events make systems more resilient, more scalable, and frankly, more fun to build.
*4. Lesson Four: Observability is your oxygen
*
When our services went live, everything seemed fine until one failed silently. No errors, no logs, just… silence. We had no idea where the request went. That’s when it hit me:
In a distributed system, if you can’t see it, you can’t fix it.
To tackle this, I was introduced to structured logging to capture important data points in a consistent format, making it easier to track issues. We also set up centralized log management to collect logs from different services in one place for quick analysis. Additionally, we implemented distributed tracing using Zipkin, which allowed us to track requests as they moved through multiple services, helping us pinpoint failures and understand system behaviour more effectively.
Lesson? Don’t wait for a production issue to make your system observable. Bake it in from day one.
Takeaway: Observability isn’t optional in microservices. It’s your survival kit.
*5. Lesson Five: CI/CD is your best friend (and your safety net)
*
At first, I couldn’t understand the need for CI/CD. I thought I could deploy everything manually without much trouble. “Why take on the extra headache of learning CI/CD when I can just do it myself?” But soon, the complexity hit — managing multiple services, their test cases, security scans, and deployment steps manually became overwhelming. Deployments were error-prone, slow, and difficult to scale
We automated builds, added unit/integration tests, set up health checks, and created deployment strategies using Jenkins.
Now? A developer can make a change, push to main, and see it go live safely-without breaking anything else. That confidence is priceless.
Takeaway: In microservices, good CI/CD isn’t a luxury — It’s your stability, your speed, and your peace of mind.
Migrating to microservices taught me that great architecture isn’t about perfection, it’s about enabling evolution. Yes, it comes with challenges. But with the right mindset, tools, and collaboration, it brings a kind of freedom that’s hard to describe.
Looking back, I smile at the days I thought the monolith was the only way. It served its purpose, but now, I’ve seen what’s possible when services are independent, resilient, and built with purpose.
I feel genuinely lucky to have been part of that migration journey. If I hadn’t been in the trenches, it would’ve taken me a lot more time and probably a lot more trial and error to truly understand microservices. Being hands-on gave me insights that no amount of reading could replace, and it’s the reason I can share these experiences now with both honesty and a bit of nostalgia.
Each lesson served as a stepping stone in my journey, and even though I’ve come a long way, I’m still learning every day. Microservices, event-driven design, and CI/CD aren’t static topics, they evolve, and I’m right there, growing along with them.
To all the Java devs standing at the edge of this transition:
Don’t fear the split. Embrace the change. Just do it mindfully, one service at a time.
I’d love to hear your own migration stories or answer any questions if you’re just getting started. Let’s learn from each other. :)
Follow me for more stories, lessons, and practical tips from the world of Java and cloud-native development.