Technology · Languages & frameworks

Java and Spring Boot for the systems that cannot afford to be wrong.

Java and Spring Boot engineers for transactional cores, high-throughput event processing and the modernisation of estates that already run on the JVM — banking, telco, logistics and airline systems.

JavaWhat we build with it
01Transactional cores
02High-throughput event processing
03Domain services in a modular monolith
04Integration and API layers
05Legacy re-engineering
06Batch and scheduled processing
Usually runs with
  1. Java 21
  2. Spring Boot
  3. Spring Kafka
  4. Spring Batch
  5. PostgreSQL
  6. Kafka
Why we use it

The language of systems that cannot afford to get it wrong.

Java is the language of the systems organisations cannot afford to get wrong: core banking ledgers, billing, loyalty accrual, load settlement. It runs the transactional and event-processing services in our own platforms alongside TypeScript, and it is the language many enterprise estates are already written in. The JVM gives predictable performance under sustained load, mature garbage collection, and decades of tooling for diagnosing a running system.

Spring Boot is how we make Java productive. Auto-configuration, a well-understood dependency-injection model, first-class support for Kafka, PostgreSQL, security and observability, and a convention that lets an engineer move between codebases without relearning the project. Virtual threads in recent Java releases have closed most of the gap with asynchronous runtimes for I/O-heavy services without giving up the simple thread-per-request model.

It is not the default for everything. A small BFF, a content site or a data pipeline is usually better served by TypeScript or Python, and we say so. Java earns its place where correctness, throughput and a long operational life matter more than the speed of the first prototype.

What we build with it

Six things we ship on Java and Spring Boot.

01

Transactional cores

Ledgers, accrual and settlement engines, order and policy management — services where every write is idempotent, audited and reconcilable, and where a bug is a financial event rather than a support ticket.

02

High-throughput event processing

Kafka consumers and stream processors that rate, enrich and route events at high volume, with exactly-once semantics where the domain demands it and a documented answer for when it does not.

03

Domain services in a modular monolith

Spring Modulith or a disciplined package structure that keeps a large codebase legible, with explicit module boundaries you can later split into services — or deliberately never do.

04

Integration and API layers

REST and gRPC services in front of SAP, core banking, carrier and payment systems, with resilience patterns — circuit breakers, bulkheads, retries with jitter — configured rather than improvised.

05

Legacy re-engineering

Strangler-pattern replacement of Java EE, Struts or vendor platforms: characterisation tests first, then a route or a capability at a time onto Spring Boot, with the old and new running side by side until the numbers match.

06

Batch and scheduled processing

Spring Batch jobs for end-of-day, statement generation and reconciliation — restartable, chunked, observable, and designed so a failure partway through does not mean starting again.

Fit

When to choose it.

Right fit

Choose Java and Spring Boot for services that will live for many years, process money or regulated data, or must hold their latency under sustained CPU load. Choose it when your organisation already runs the JVM and has the people, monitoring and security tooling to match — adding another runtime costs more than it returns. It fits large teams well: the conventions are strong enough that a codebase stays coherent as engineers change.

Look elsewhere

Do not choose it for a small prototype, a marketing site or a thin BFF where TypeScript is the simpler fit, or for data science and LLM work where Python owns the ecosystem. And do not modernise a working Java 8 monolith by rewriting it in a new language — the honest path is usually an upgrade, a modular structure and a strangler pattern, which we will lay out before any code is written.

In practice

An outbox event designed not to be lost or duplicated.

SettlementService.javajava
@Transactional
public Settlement settle(SettleCommand cmd) {
    var existing = settlements.findByIdempotencyKey(cmd.key());
    // a retried command returns the original result, never a second write
    if (existing.isPresent()) return existing.get();

    var settlement = ledger.apply(cmd);
    settlements.save(settlement);
    // written in the same transaction; a relay publishes it to Kafka
    outbox.append(SettlementCompleted.from(settlement));
    return settlement;
}
Reference architecture

Where Spring Boot sits in an Odeontech estate.

Edge
API gatewayNext.js / BFFPartner APIs
Services
Spring Boot domain servicesStream processorsSpring Batch
Messaging
KafkaTransactional outboxDebezium CDC
Data
PostgreSQLOracle / SQL Server (legacy)Redis
Platform
KubernetesOpenTelemetryMicrometerGitLab CI / GitHub Actions
Our standards

What the Java codebases we hand over look like.

  • Current LTS, on purpose. Java 21 or the latest LTS, Spring Boot 3.x, with an upgrade path written into the runbook rather than discovered at the next CVE.
  • Transactions and idempotency designed, not assumed. Every command that changes money, stock or status carries an idempotency key; every event leaves through a transactional outbox.
  • Tests that mirror the risk. JUnit for the domain, Testcontainers against a real PostgreSQL and Kafka, contract tests for every integration, and Gatling where throughput is a requirement.
  • Observability from the first commit. Micrometer metrics, OpenTelemetry traces and structured logs with correlation ids, with SLO dashboards before the service takes traffic.
  • Resilience configured. Timeouts on every outbound call, circuit breakers and bulkheads via Resilience4j, and retries that back off with jitter rather than hammer a failing dependency.
  • Security in the pipeline. Dependency scanning, SAST, an SBOM per build, and Spring Security configured from a reviewed baseline rather than copied from a tutorial.
  • Schema migrations versioned. Flyway or Liquibase, forward-only, tested against a production-shaped copy — the database changes with the code, not ahead of it.
  • Containers built once. Reproducible images with pinned base layers and JVM flags tuned for the container’s memory limit, promoted unchanged from staging to production.
Typical pairings

What usually ships alongside it.

  • A Spring Boot service template with security, validation, tracing, health checks and Testcontainers already wired
  • Kafka topics with versioned schemas in a registry and a transactional outbox pattern in the codebase
  • Flyway migrations and a database change process the DBA has signed off
  • Characterisation tests and a parallel-run harness when the work is modernisation
  • Runbooks, SLO dashboards and JVM diagnostics the on-call engineer can use without the original author
Runs with
Java 21Spring BootSpring KafkaSpring BatchPostgreSQLKafkaDebeziumFlywayTestcontainersResilience4jMicrometerKubernetes
FAQ

Frequently asked questions

Usually a modular monolith first. Explicit module boundaries inside one deployable give most of the benefit — clear ownership, independent testing — without the operational cost of a distributed system. We split out a service when a module has a real reason: separate scaling, a different release cadence, a different team. Not because a diagram looked better.
Yes, and it is often the right first step. We inventory dependencies, upgrade in steps with the test suite as the safety net, and handle the javax-to-jakarta migration that comes with Spring Boot 3. The plan — steps, order, what is tested at each one — is written before the work starts, and at the end you are on a supported runtime.
Transactional outbox for producers, idempotent consumers keyed on event id, and Kafka transactions only where the domain truly needs exactly-once processing. We document which guarantee each topic carries so the consuming team is not guessing.
It does not have to be. Layered images, lazy initialisation where safe, CDS archives and — where cold start really matters — GraalVM native images bring start-up time down. We measure it against your scaling policy rather than assume.
Yes. We start with an audit — versions, tests, build, observability, security posture — and give you the findings in writing. Then we work in your repositories, your CI and your review process, raising the bar as we go. Rewrites are proposed only when the audit justifies one.
Related

Neighbouring technologies and the services that use them.

Need Java engineers for systems under load?

Tell us whether the work is a new transactional service, an event pipeline or a modernisation, and the seniority you need. We will propose a named team or place specialists into yours.

✓ No pitch deck✓ Reply from an AI-native engineer✓ Written follow-up