Let's talk

Backend

Hire Java Developers

Java engineers who maintain and modernise the services larger organisations genuinely depend on.

Java earns its place where a system has to run for a decade, be understood by people who did not write it, and survive an audit. The tooling around it is the real argument — profilers, heap dumps, mature messaging and deployment paths that let you diagnose a production problem instead of guessing at it.

Most commercial Java work is not building a new service; it is keeping a valuable old one supportable. Hiring for this stack goes wrong when the brief is written as though it were greenfield, because the skills that matter are archaeology, incremental migration and performance diagnosis rather than framework fluency.

What the javax to jakarta rename actually costs

The Jakarta EE namespace change renamed the packages under javax.* to jakarta.*. Mechanically it is find and replace, and every team underestimates it for exactly that reason. The cost is not the imports. It is that a library you depend on has to have made the same move, at a version compatible with your Spring Boot version, at the same time — and any library that has not is now your problem.

Spring Boot 3 requires that namespace and a minimum of Java 17, so in practice three migrations arrive as one: JDK, framework and namespace. The teams that survive it sequence those separately — JDK first, on the old framework, then the framework — rather than attempting the lot in a single branch that cannot be merged for four months.

What an inherited Java estate actually looks like

Descriptions of this work are usually too polite about the starting position, so here is a real one from an application we picked up: Spring Boot 2.6 on Java 8, QueryDSL generating query classes at build time, encrypted application properties, a single test class, and the schema managed by Hibernate’s automatic update rather than by any migration tool — which means nobody can say what the production schema is except by looking at it.

The most instructive thing in it was the security configuration. The filter chain was told to ignore the entire API path prefix, with authorisation instead applied by an annotation and an aspect. That works precisely as long as every new controller method remembers the annotation, and it fails silently the first time one does not. A security control that depends on an author remembering it is a convention, not a control — and finding out which of the two you have inherited is the first hour of work on any estate like this.

Where the performance problems actually are

Java performance work rarely turns out to be the JVM. It is almost always the database access layer, and the same three causes recur.

The first is N+1 selects: one query for a collection and one more per element, invisible in code because the loop looks like ordinary field access. The second is spring.jpa.open-in-view, which defaults to true and keeps a persistence context open for the whole request, so lazy loading succeeds in production and hides the first problem until the day it does not. The third is a transaction boundary drawn around something that waits — an HTTP call inside @Transactional holding a connection from a pool of ten while a third party takes four seconds.

Turn on SQL logging in a test that exercises a real endpoint and count the statements. It is the cheapest diagnostic in this ecosystem and it finds more than any profiler will.

Virtual threads, and the honest caveat

Virtual threads landed properly in Java 21 and they genuinely change the arithmetic for services that spend their time waiting. They are not a free upgrade. A virtual thread that blocks inside a synchronized block pins its carrier thread, which means a codebase full of legacy synchronisation can adopt them and get nothing, or get worse. Thread-local caching patterns that assumed a small fixed pool also stop being cheap when there are a hundred thousand threads.

An engineer who has actually used them will raise those caveats unprompted. One who has read the release notes will describe the throughput graph.

Where Java is the wrong choice

It is the wrong choice for a small service written once and rarely touched, where the ceremony costs more than it returns. It is the wrong choice where cold start time is the binding constraint and the deployment model is short-lived functions, unless someone is prepared to take on native compilation and the reflection configuration that comes with it. And it is a poor fit for a team of two who will be the only people ever to read the code — the language’s advantages are advantages at organisational scale, and at that size they are simply overhead.

What we interview for

We ask about the upgrade path a candidate has lived through, and we listen for sequencing. How did they keep feature work moving while the migration ran, what did they do about a dependency with no Jakarta release, how long did the application run on two versions at once, and what did they use to prove behaviour had not changed.

Then a diagnostic pair. The service is slow but CPU is idle — what do you look at. The heap grows across a week and a restart fixes it — what do you capture, and what tells you the difference between a leak and a cache doing its job.

Engineers who have operated a JVM answer with thread dumps, pool exhaustion and heap histograms. Engineers who have only written features answer with a framework upgrade.

How AI assistance is used on a codebase this old

The mechanical half of an upgrade is where assistance pays: the namespace change across thousands of files, deprecated API replacements, configuration property renames, and the characterisation tests that have to exist before any of it is safe to merge.

What is not delegated is anything to do with concurrency or transaction boundaries. A suggestion that removes a synchronized keyword, widens a transaction or swaps an isolation level will compile, pass the tests, and fail under load six weeks later — which is the failure profile a reviewer has to assume is present rather than hope is not.

Teams are built for companies in the United States and the Gulf — the UAE, Saudi Arabia, Qatar, Kuwait, Bahrain and Oman. The engineers are in Pune, which matters mostly for the clock. Dubai is ninety minutes behind us and Riyadh two and a half hours, so a Gulf team shares almost the whole working day. New York is nine and a half hours behind, so American engagements run on a written handover and one fixed overlap window rather than on a standing call — a real constraint, and better stated than discovered.

What these engineers do

  • Spring Boot 3 with Jakarta EE namespaces, Spring Security and Spring Data
  • JPA and Hibernate mapping, N+1 diagnosis and query tuning against real data
  • Java 17 and 21 features - records, sealed types, pattern matching and virtual threads
  • Kafka or RabbitMQ messaging with delivery guarantees designed rather than assumed
  • JVM profiling, garbage collection tuning and heap analysis on live systems

Delivered AI-first

AI assistance is applied hardest to upgrade work, which is most of what Java teams are asked to do - the javax to jakarta namespace change across thousands of files, Spring Boot 2 to 3 configuration migration, replacing deprecated APIs and modernising verbose code to records and pattern matching. It also drafts the test coverage that has to exist before any of that is safe. Concurrency and transaction boundaries stay human decisions. Review discipline is unchanged. The measurable effect is throughput per engineer, not fewer reviews.

Tell us what the Java work is.

Roughly what it involves, the seniority you need, and when it has to start. We will say what it takes to staff it, or say honestly that we are not the right people for it.

A person reads every enquiry and replies within one working day.