Modernizing a large EJB-based system is rarely a technical problem alone. It’s a business risk problem.

When the codebase is measured in hundreds of thousands of lines and supports real revenue, the goal isn’t elegance—it’s continuity. The business must keep operating while the system changes underneath it.

This article outlines a practical, field-tested approach to migrating large EJB systems to Spring Boot without disrupting ongoing business operations.


The Real Risks in Large-Scale Migrations

Most migrations fail for reasons that have little to do with frameworks.

Common failure points include:

  • Underestimating hidden dependencies and runtime behavior
  • Treating migration as a “big rewrite” instead of a controlled transition
  • Optimizing for speed instead of reversibility
  • Lack of a rollback strategy when things go wrong

In large enterprise systems, stability matters more than novelty. Any successful migration plan must start there.


Core Principles

Principle #1: Preserve Business Behavior First

Before changing technology, lock down what the system actually does today.

In practice, this means:

  • Identifying core business flows that generate revenue or meet regulatory obligations
  • Capturing current behaviour with integration and regression tests
  • Treating undocumented behaviour as a feature, not a bug

In one large migration (600K+ LOC), the most valuable asset wasn’t documentation—it was production behaviour observed through logs, metrics, and real user traffic.

Principle #2: Avoid the Big Bang Rewrite if possible

A full rewrite is appealing—and usually catastrophic. Instead, successful migrations use incremental replacement:

  • Keep the existing EJB system running
  • Introduce Spring Boot services alongside it
  • Gradually move responsibilities from the legacy system to the new platform

This approach allows:

  • Continuous business operation
  • Controlled risk exposure
  • Measurable progress instead of all-or-nothing deadlines

One of the biggest obstacles of EJB and Spring co-existence is to make sure the beans share the same transaction in the call flow. Unfortunately, this is container implementation specific. Consult the manual of your EJB container to see how to configure Spring to use the same transaction manager.

Principle #3: Define Clear Migration Boundaries

Large EJB systems often suffer from blurred responsibilities.

Before writing Spring Boot code:

  • Identify seams where functionality can be isolated
  • Prefer boundaries aligned with business capabilities, not packages
  • Accept that boundaries will be imperfect at first

Early boundaries don’t need to be perfect—they need to be stable enough to evolve. We rely on domain knowledge rather than framework expertise to guide us.

Principle #4: Build a Compatibility Layer, Not a Translation Layer

One of the biggest mistakes is trying to replicate EJB patterns in Spring Boot.

Instead:

  • Adapt legacy contracts at the edges
  • Normalize data models and error handling early
  • Let Spring Boot services follow modern design practices

Think of the migration as coexistence, not translation.

This reduces long-term complexity and avoids carrying legacy constraints into the new architecture.

Principle #5: Invest Early in Observability

You cannot safely migrate what you cannot see.

Before traffic moves:

  • Add structured logging across legacy and new services
  • Introduce distributed tracing where possible
  • Establish baseline performance and error metrics

During one migration, observability exposed hidden synchronous calls that would have caused cascading failures if moved blindly.

Visibility turns unknown risks into manageable decisions.

Principle #6: Make Rollback Boring

Every migration step should be reversible. Keep your PR and commits as clean as possible.

Principle #7: Expect Dual Maintenance (Temporarily)

For a period, you may need to support two systems. Plan explicitly for:

  • Parallel fixes in legacy and new components
  • Clear ownership boundaries
  • A defined end state for retiring legacy functionality

Ignoring dual maintenance leads to rushed cutovers and avoidable outages.


Technical Considerations

Once the high-level principles are clear, the real work begins. This is where many migrations stumble—not because the technology is difficult, but because subtle runtime assumptions get lost in translation.

Application Configuration

Traditional EJB-based enterprise applications rely heavily on container-managed configuration. It’s common to see critical settings spread across multiple XML files—application.xml in the EAR, ejb-jar.xml for EJB definitions, and web.xml for servlet configuration.

Spring Boot consolidates this model. Configuration typically lives in a single application.yml, with profile-specific overrides such as application-dev.yml or application-prod.yml.

During migration, the risk isn’t the format change—it’s what gets forgotten.

Data source configuration is a frequent example. Connection pool sizing, timeouts, keep-alive behavior, and retry semantics are often defined deep inside the EJB container and implicitly relied upon in production. When moving to Spring Boot, these settings must be made explicit and carried forward deliberately. Missing one value can quietly degrade performance or stability long after the migration appears “successful.”

The goal is functional equivalence first, not optimization.

Properties Files

Legacy enterprise systems often load configuration from external property files using mechanisms such as Apache Commons Configuration or Java Resource Bundles. These files may be referenced throughout the codebase, sometimes in places that are no longer obvious.

In Spring Boot, this responsibility naturally shifts to application.yml, which becomes the single source of truth for configuration.

For large systems, a hard cutover is rarely realistic. One practical transition strategy is to bridge existing configuration access rather than rewrite everything at once. If Apache Commons Configuration is already in use, a custom configuration adapter can be introduced to delegate property lookups to Spring’s ApplicationContext, with a fallback to existing property files.

This allows legacy code to continue functioning while configuration gradually moves into Spring-managed infrastructure.

One important caveat: Spring’s ApplicationContext is not available during static initialization. Any code that depends on configuration values at class-load time will need to be refactored—typically by deferring initialization to @PostConstruct or resolving configuration lazily at runtime.

This kind of refactoring is rarely glamorous, but it is essential for a safe migration.

EJB/J2EE to Spring Mapping

J2EE ConceptsSpring Boot EquivalentMigration Nuances
@Stateless@Service/@Component/@RepositoryCarry over the corresponding transaction attributes to the @Transactional annotation
@StatefulSame as @StatelessSpring doesn't support passivation.
@Named without scope@Service/@Component/@Repository with @Scope(SCOPE_PROTOTYPE)
@Named with scope@Component
@ApplicationScoped@ApplicationScope
@SessionScoped@SessionScope
@RequestScoped@RequestScope
@Singleton with scope@Service/@Component
@TransactionAttribute@Transactional
@Timer@EnableScheduling
@InterceptorConvert to an AOP class using @Aspect
@Asynchronous@Async
JNDI Lookups@Autowired or @ValueReplace Context.lookup() with Spring Dependency Injection.

Other Dependencies (Modernize with Intent)

Not every dependency needs to be addressed at the same time—or even during the core migration. Some components can be modernized in advance, while others are better handled incrementally once the system is stable.

Dependencies such as logging frameworks, Hibernate, and JPA are often tightly coupled to the application server in legacy environments. Changing them blindly adds risk; leaving them untouched can limit visibility during the migration.

The practical approach is selective modernization: upgrade dependencies early when it improves observability or reduces operational risk, and defer changes that don’t directly support continuity. Sequencing matters more than completeness.

Final Thoughts

Large EJB-to–Spring Boot migrations are marathons, not sprints.

The teams that succeed focus less on frameworks and more on:

  • Risk management
  • Observability
  • Incremental progress
  • Respect for existing business behavior

That mindset—not tooling—is what keeps the business running while the platform changes.


About Ambrose Software

I work with organizations on short-term, outcome-driven engagements to modernize and stabilize complex enterprise systems while maintaining business continuity.