Create a Custom Auto-Configuration with Spring Boot

From a Java EE Monolith to Microservices: Lessons from Modernizing a Port Management System

After years of working with legacy systems, I had the opportunity to experience a major transformation: helping modernize a large port management system from a Java EE monolith into a microservices-based architecture.

It was not a simple rewrite.

The system was deeply connected to critical port operations, contained years of accumulated business rules, and had integrations with Navis N4, an enterprise system used to manage important aspects of port operations.

The goal was ambitious: gradually replace the monolith until it could eventually be retired.

Looking back, this project was one of the experiences that most shaped the way I think about backend engineering.

The Legacy System

The original application was a large monolith built with Java EE 8.

Some parts of the system also relied on older technologies such as Adobe Flex, developed with Flash Builder, as well as JSP.

The need for modernization became increasingly clear.

Adobe Flex and Flash Builder were discontinued technologies, and the Java version also needed to evolve. Instead of simply upgrading the existing application, the team decided to build a new architecture using Java 17 and Spring Boot.

But there was an important constraint:

The existing system could not simply be turned off.

The port operation had to continue.

The legacy system needed to remain running while the new architecture was being developed.

That changed the entire approach to the modernization.

The Goal Was to Eliminate the Monolith

The strategy was not to create a few microservices and leave the monolith running forever.

The idea was to gradually transform the existing modules into microservices and eventually eliminate the monolith.

Each module of the existing system would become a microservice.

The migration was performed incrementally.

A new microservice would be developed, tested, and gradually released to production. The team would validate its behavior in the real environment before eventually disabling the corresponding functionality in the monolith.

Then the process would continue with another module.

It was essentially:

Monolith → Microservice → Production validation → Legacy module retired

And then the process repeated.

This approach reduced the risk of making one massive change to a business-critical system.

The Biggest Challenge Was Not the Technology

One of the biggest lessons from the project was that modernizing a legacy system is not only a technical challenge.

The hardest part was understanding the existing business rules.

The legacy application had been running for years, and many of its behaviors were embedded directly into the code.

There were services containing queries and business logic in ways that had accumulated over time, creating what could reasonably be described as spaghetti code.

Before rebuilding functionality, we needed to understand what the existing system actually did.

Sometimes the code was the only documentation available.

This meant that modernization required much more than translating old code into new code.

We had to understand the business behavior first.

Only then could we rebuild that functionality in a cleaner architecture.

Building the New Microservices

The new microservices were developed using Java 17 and Spring Boot.

They followed a layered architecture with clear responsibilities.

A simplified representation was:

Controller → Service → Repository → Database

The services used Spring Data/JPA for persistence, entities for the database model, repositories for data access, and services for business logic.

The APIs were exposed through REST controllers and documented using Swagger.

Security and configuration were also separated appropriately, including the mechanisms required for communication between services.

Compared with the old monolithic application, the difference was significant.

Instead of having business responsibilities mixed together inside a large application, each microservice had a defined responsibility.

The architecture was not created in isolation, though.

It had to coexist with the legacy application during the migration.

The Shared Database Reality

One of the most interesting parts of this modernization was the database.

During the migration, the legacy application and the new microservices used the same database and the same tables.

However, they did not share the same API endpoints.

The new microservices were built with a much cleaner architecture, while the legacy application continued operating using its existing implementation.

From a theoretical microservices perspective, a shared database is not the ideal end state.

But real modernization projects often have constraints that architecture diagrams do not show.

The priority was to gradually extract functionality from the monolith while keeping the business operating.

This was a pragmatic transition architecture.

The goal was not to create a perfect microservices environment on day one.

The goal was to move the system forward without breaking the operation.

The Navis N4 Integration

Another major part of the project was the integration with Navis N4.

The port management platform communicated with Navis in both directions.

Data was sent to Navis and data was also received from it.

The integrations used APIs, including REST and, in some modules, SOAP.

This was particularly challenging because the two systems had different business rules, data representations, and synchronization requirements.

But there was another problem that made the integration significantly harder:

There was very little documentation available for Navis.

In many situations, understanding how the integration worked became an investigation process.

We had to understand how Navis behaved, test different approaches, analyze the results, and gradually build knowledge about the integration.

It was often a process of trial and error.

This was also where understanding the business domain became extremely important.

Navis was deeply connected to the port operation, including concepts such as containers, vessels, voyages, scheduling, and other operational processes.

You could not treat the integration as simply moving data from one API to another.

You needed to understand what that data actually meant.

Learning APIs Through Microservices

Working on this project was also a major step in my evolution as a backend developer.

I had already worked with backend systems before, but this project gave me a much deeper exposure to modern backend architecture.

I started working much more intensively with APIs in a microservices environment.

I learned that building a backend service was not only about implementing business functionality.

It also meant thinking about how services communicate, how responsibilities are separated, how data is persisted, how security is handled, and how a service can evolve independently.

Swagger also became an important part of documenting the APIs and making the contracts easier to understand.

These were lessons that went beyond a particular framework or technology.

Docker, Kubernetes and Observability

The project also introduced me more deeply to technologies that were becoming increasingly important in modern backend environments.

The microservices ran in containers, giving me practical exposure to Docker-based environments.

I also had contact with Kubernetes mainly from the perspective of application support and operation rather than infrastructure administration.

I was not responsible for creating Dockerfiles or configuring Kubernetes deployments.

Most of my involvement was related to supporting the running services and investigating issues.

For example, monitoring logs and service health became part of the daily work.

Grafana was used as part of the observability environment.

This was another important learning experience for me.

Building a microservice is only one part of backend engineering.

You also need to understand what happens after the service is deployed.

Supporting the Legacy While Building the Future

Perhaps the most important characteristic of this project was that we were building the future while still supporting the past.

The legacy system continued operating.

The new microservices were developed alongside it.

The same database was used during the transition.

New services were gradually released to production.

And, once a new implementation had been validated, the corresponding legacy functionality could finally be retired.

This required patience and a strong understanding of the existing system.

It also required collaboration.

Modernization is rarely something a single developer can accomplish alone, especially when the system is connected to critical business operations and external enterprise platforms.

What This Experience Taught Me

This project changed the way I look at backend engineering.

Before this experience, I naturally focused more on building functionality and solving the immediate technical problem.

After working on this modernization, I started looking at backend systems more broadly.

Architecture matters.

Business rules matter.

APIs matter.

Integration design matters.

Observability matters.

And, perhaps most importantly, understanding the system you are changing matters.

I also gained valuable knowledge of the port operations domain.

Working closely with such a specialized business environment taught me that software engineering is not only about technology.

A developer can know Java, Spring Boot, REST, JPA, Docker, and microservices, but without understanding the business problem, it is difficult to build the right solution.

Modernization Is a Journey

One of the biggest misconceptions about legacy modernization is that the solution is simply to replace the old technology with a new technology.

In reality, the difficult part is preserving the business while changing the technology underneath it.

Our migration was gradual.

The monolith and microservices coexisted.

They shared the database.

The new architecture was cleaner, but the old system still had to operate.

The Navis integration required investigation because documentation was limited.

And each new service had to prove itself in production before the corresponding legacy functionality could be retired.

It was not a perfect architecture from day one.

It was a real architecture evolving under real constraints.

And that is exactly what made the experience so valuable.

Looking Back

The modernization of that port management platform was an important milestone in my backend engineering career.

It was where I gained deeper experience with Java 17, Spring Boot, REST APIs, microservices, JPA, Swagger, Docker, observability, and modern software architecture.

More importantly, it taught me how to approach complex legacy systems.

You do not modernize a system simply by replacing its technology.

You modernize it by understanding its business rules, defining clear responsibilities, introducing better architecture incrementally, validating changes in production, and continuously reducing the dependency on the legacy system.

That experience continues to influence how I approach backend engineering today.

Because in real-world software engineering, modernization is rarely about starting from scratch.

It is about building the future while keeping today’s business running.

Deixe um comentário