Introduction

What if 80% of your microservices should actually be a modular monolith?

Over the weekend, a particular train of thought captured my attention—patterns in system behavior and design. As we transition from mainframes to modern Java architectures and tackle modernization initiatives, a question keeps surfacing: How much are we just reinventing the wheel?

This piece dives deep into replacement, focusing on modular monoliths, centralized transactions, and RACF-style authorization. What follows are exploratory notes—not definitive opinions, but a structured examination that continues to evolve.

Modern cloud-native frameworks optimize for different goals: developer velocity, containerization, and horizontal scaling. But in doing so, have we sacrificed something important? The overarching lesson seems to be that modern systems often layer complexity to compensate for flexibility, whereas the mainframe principles of discipline, cohesion, and predictable transactions remain highly effective when consciously applied.

So the question isn’t whether we can modernize mainframe applications. The question is: Will we have the discipline to do it right?

The Modern Stack for Mainframe Replacement

When replacing a COBOL/CICS/DB2 mainframe application, the guiding principles are usually:

  1. Correctness over cleverness
  2. Strong transactional boundaries
  3. Explicit domain modeling
  4. Incremental migration
  5. Boring tech wins

Technology Stack

  • Runtime: Quarkus (JVM mode initially)
  • Architecture: Modular Monolith
  • Domain: Clean Architecture / DDD
  • Persistence: JPA + RDBMS (PostgreSQL/Oracle/DB2)
  • Transactions: JTA (Narayanas)
  • APIs: JAX-RS
  • Messaging: Kafka
  • Security: OIDC / OAuth2
  • Deployment: Containers or VMs

Why Modular Monolith First?

Not microservices on day one. Here’s why:

  • One deployable unit with strict module boundaries
  • Internal APIs enforced at compile time
  • Horizontal scaling comes later, after correctness is proven
  • Mainframes are monoliths for good reason—cohesion matters

The “Centralized Hybrid” Pattern

A team initially tried a “microservices-first” approach. The result was a proliferation of network latency and “distributed transaction” nightmares that the mainframe simply never had. The solution? Pivot to a Modular Monolith running on Quarkus.

By centralizing the transaction manager (JTA) and strictly enforcing domain boundaries within a single deployable unit, they achieved:

  • 30% reduction in operational complexity.
  • Sub-millisecond latency for intra-module calls (replacing REST/gRPC).
  • Consolidated logging and auditing that mirrored the reliability of the original mainframe environment.

The takeaway was clear: Scale the region, not the individual program, until you have a genuine organizational reason to split.

Replacing CICS and DB2

What CICS Actually Provided

People often underestimate CICS. It wasn’t just “an app server”:

  • Ultra-low-latency OLTP
  • Millions of short-lived transactions
  • Strong ACID semantics
  • Terminal/session handling
  • Integrated security and resource management
  • Extreme reliability

Modern CICS Replacement

The replacement isn’t one thing—it’s a stack:

  • CICS program: Java service / use-case class
  • CICS transaction: REST endpoint or message handler
  • COMMAREA: Request DTO / command object
  • Syncpoint: JTA transaction
  • TSQ / TDQ: Database table / Kafka topic
  • BMS screen: Web UI / API consumer
  • CICS region: App instance / pod
  • CICS security: IAM + OAuth + RBAC

Key insight: CICS transactions map cleanly to short-lived stateless service calls. The challenge is recreating CICS’s predictability, not its APIs.

Database Workload Replacement

For online (OLTP) database access:

  • Stick with relational databases (PostgreSQL, Oracle, DB2 LUW)
  • Use JPA (Hibernate) or plain SQL for migrated logic
  • Do not get creative with NoSQL for core state

For batch database workloads:

  • Spring Batch (still best in class)
  • Explicit transactions with checkpointing and restartability
  • Scheduling via Control-M, Autosys, or carefully managed Kubernetes CronJobs
  • Batch is not microservices—treat it like batch

The Paradox: Why Are Replacements So Complex?

If CICS was so compact and effective, why do modern replacements require so many components?

CICS Was Vertically Integrated

CICS provided a single integrated universe:

  • Runtime, scheduler, transaction manager, lock manager
  • Security model, I/O subsystem, UI protocol (3270)
  • Operations tooling, recovery system
  • All designed together for one OS, one hardware model, one trust boundary

Modern systems deliberately don’t do this. We chose flexibility over cohesion.

Locality vs. Distribution

CICS assumptions:

  • Everything is local
  • Memory is shared
  • Latency is microseconds
  • Failure is rare and restart is controlled

Modern assumptions:

  • Network calls are normal
  • Processes die frequently
  • Machines are ephemeral
  • Horizontal scaling is mandatory
  • Failure is expected

You can’t have a single “compact” component when the world is not a single box anymore.

Hidden Complexity vs. Explicit Complexity

CICS handles locking, deadlock detection, recovery logging, and rollback implicitly. Modern replacements:

  • Make these concerns explicit
  • Expose them as separate components
  • Force you to reason about them

Less magic, more knobs. Neither approach is inherently wrong—they optimize for different goals.

Cloud Services: Mainframe Concepts in Disguise

If you squint, the cloud is basically IBM circa 1978 with better UX:

  • AWS Lambda / Azure Functions: CICS transactions
  • RDS, Cloud SQL, Aurora: DB2 / VSAM datasets
  • SQS, Pub/Sub, Service Bus: TSQ / TDQ
  • Step Functions, Durable Functions: JCL workflows
  • IAM, Azure AD: RACF
  • CloudWatch, Stackdriver: SMF / RMF
  • Auto-scaling groups: Workload Manager

The punchline: Cloud re-bundled mainframe ideas and is selling them per hour.

Building a Modular Monolith That Scales Like CICS

Core Structure

app/
├── core-domain/
│   ├── account/
│   ├── ledger/
│   ├── customer/
├── application/
│   ├── usecases/
│   ├── transactions/
├── infrastructure/
│   ├── persistence/
│   ├── messaging/
│   ├── security/
├── interfaces/
│   ├── rest/
│   ├── batch/
│   ├── messaging/

Rules:

  • Core domain has zero framework dependencies
  • Modules communicate via interfaces only
  • No shared mutable state across modules
  • One transaction manager (JTA)
  • Explicit boundaries everywhere

CICS-Style Transactions

@Transactional
public void postPayment(PaymentCommand cmd) {
    validate(cmd);
    debitAccount();
    creditAccount();
    writeLedger();
}

No retries inside. No async in the middle. No cleverness. Just like CICS.

Concurrency Model

CICS’s secret sauce:

  • Short transactions with strict limits
  • Back-pressure and early rejection
  • Bounded thread pools and queue depth limits
  • Timeouts everywhere

Reject work early. Mainframes did this ruthlessly—we should too.

Scaling Strategy

Vertical first:

  • Optimize single-instance throughput
  • Tune GC and avoid chatty I/O

Horizontal second:

  • Clone the whole monolith
  • Stateless instances
  • Sticky sessions if absolutely necessary

Scale the region, not the program.

Authorization: RACF-Style for Modern Systems

One critical capability from mainframes that deserves special attention is RACF-style authorization. RACF wasn’t just “login + roles”—it was a centralized policy engine that decided whether a subject could perform an action on a named resource, consistently and auditably, outside application code.

Modern applications can achieve the same discipline using tools like Keycloak:

public class AccountService {
    private final AuthorizationService auth;

    public void withdrawMoney(Subject subject, String accountId, BigDecimal amount) {
        // Authorization at use-case boundary
        auth.check(subject, Resource.account(accountId), Action.UPDATE);
        
        // Business logic follows
        // ...
    }
}

Core principles:

  • Centralized enforcement - One authorization service, not scattered checks
  • Resource-based permissions - ACCOUNT:12345, not just role names
  • Data-driven policies - Stored in Keycloak, not hardcoded in annotations
  • Default deny - Nothing allowed unless explicitly permitted
  • Full audit trail - Every decision logged for compliance

This approach gives you the maintainability and auditability of RACF with the flexibility of modern IAM systems. See my other post for more details: Implementing RACF-Style Authorization in Modern Java Applications**

What Modern Complexity Is Actually Unnecessary

Let’s be brutally honest about which modern patterns add more cost than value:

1. Microservices for Intra-Domain Logic

The claim: Independent deployment, team autonomy, scalability!

The reality:

  • Network calls replace method calls
  • Transactions get hand-waved away
  • Data consistency becomes “eventual”
  • Debugging becomes archaeology

Honest take: 80% of microservices should be modules in a monolith. Use services only at true organizational boundaries, not technical ones.

2. Event-Driven Everything

Events aren’t free—they’re delayed complexity.

Good use cases: Integration, auditing, notifications, long-running workflows

Cargo cult use cases: CRUD updates, core state transitions, financial postings

Honest take: If you need a dashboard to understand state, you’re doing it wrong.

3. Kubernetes as Default Runtime

K8s is powerful, but it’s not neutral. It adds 10+ failure modes, indirection everywhere, operational tax, and YAML chaos.

CICS gave you one control plane, one scheduler, one failure model.

Honest take: If you don’t need autoscaling every week, Kubernetes is probably overkill. Most enterprise workloads are boring and stable.

4. Service Meshes

Service meshes exist because we built systems that don’t trust themselves.

Instead of designing clear boundaries, enforcing contracts, and being explicit, we added sidecars, proxies, policies, and distributed config.

Honest take: A service mesh is often a tax you pay for not having a platform. CICS didn’t need one because it was the platform.

5. Reactive Programming for Business Logic

Reactive is great for UI, streaming, and backpressure-heavy I/O. It’s terrible for transactions, money, and deterministic workflows.

Honest take: If your business logic can’t be stepped through linearly, you’ve already lost. CICS was synchronous for a reason.

What Complexity Is Justified

Let’s be fair—some complexity is unavoidable:

Modern Security Threats

Modern threat models are real. Mainframes lived inside trusted perimeters. IAM, OAuth, and zero-trust architectures are necessary.

Network Unreliability

CICS lived in a fantasy world of stable networks and local resources. We don’t get that luxury anymore. Retries, idempotency, and timeouts are sadly real.

Vendor Independence

CICS was vendor lock-in by design. Modern ecosystems pay complexity costs to avoid that. It’s a conscious trade-off.

Lessons Learned

If this were running on CICS, how many of these components would actually exist?

If the answer isn’t “far fewer,” something’s wrong.

What to Re-Centralize

If I were ruthless, I’d centralize:

  1. Transactions — one transaction manager, one logging strategy
  2. Scheduling — both online and batch
  3. Security — central auth + authorization
  4. Deployment model — fewer shapes, fewer knobs
  5. State — one authoritative source per domain

This alone removes half the moving parts.

The Brutal Truth

Modern systems are complex because:

  • We allow undisciplined architecture
  • We mistake flexibility for progress
  • We optimize for churn, not longevity

CICS enforced discipline by removing choice. We could do the same today—we just choose not to.

  • Modern complexity is often unnecessary:

    • Microservices inside a single domain
    • Event-driven logic for CRUD
    • Kubernetes, service meshes, reactive programming for transactional code
  • Real benefits can come from:

    • Centralized transaction management
    • Modular monolith discipline
    • Explicit, auditable authorization
    • Stable resource naming and policy enforcement
  • Mainframe principles of discipline, cohesion, and predictable transactions still apply and reduce operational and security complexity

Conclusion

The cloud didn’t kill mainframes—it recreated them without admitting it.

CICS worked because it:

  • Centralized what mattered
  • Distributed only when necessary
  • Enforced discipline by design

You can absolutely build that today in Java—if you’re willing to say no to 70% of modern “best practices.”

Modern Java can replace mainframes using modular monoliths, centralized transactions, and RACF-style authorization, leveraging frameworks like Quarkus and Keycloak. The key is recognizing that mainframe principles—compact, integrated, auditable, predictable systems—are still highly relevant.

The question isn’t whether we can modernize mainframe applications. The question is: Will we have the discipline to do it right?


These are exploratory notes from a weekend deep-dive into architecture patterns. This thinking will continue to evolve.

What patterns have you noticed in your modernization journeys? Share in the comments below!

If these architectural deep-dives help you, follow me for insights on enterprise architecture.

#MainframeModernization #SoftwareArchitecture #CloudNative #EnterpriseJava #TechLeadership