When a team considers a new language, the language is rarely the problem. The work is in the stack around it: a framework, an ORM, an HTTP server, a migration tool and metrics, all configured to work together in production. Documentation is often out of date, and it can take days before the first request works.
This integration cost stopped most migration discussions early. AI coding agents reduce it, so it makes sense to measure the runtimes again.
What AI changes
In a company, AI does not replace the team, design the architecture or decide a migration. It is good at integration work: it puts frameworks together, resolves dependencies, pins versions and writes working configuration. This work was the main cost of adopting a new runtime.
What is left is the question of whether another runtime is worth the change. The measurements below help to answer it.
The benchmark setup
Four microservices have the same REST endpoints, use PostgreSQL and export Prometheus metrics. They implement the same CPU-heavy business logic, so the differences under load come from the runtime.
The four services run on one Minikube cluster and share one PostgreSQL instance, with one schema per service. Prometheus scrapes all four services. Grafana loads a comparison dashboard at startup. make seed sends 500 orders to each service in parallel, across all discount tiers. That is enough load for the metrics without a separate load-testing tool.
Stack overview
Measurements
CPU under load
Java shows the usual JIT warmup curve. CPU goes up while the JIT profiles and compiles the code, then it drops below Python and Node. Rust stays low and flat from the first request. Python and Node use the most CPU during the whole run.
Python and Node run each iteration in an interpreter or a JIT with boxing, dynamic dispatch and garbage collection. Rust compiles to native code. After warmup, Java is closer to Rust than to Python and Node. Python could use C extensions such as NumPy to reduce the gap. Here the logic is plain Python, so the chart shows the cost of the runtime itself.
Memory
Under load, Java, Python and Node use 10 to 20% more memory than at idle. Rust memory stays the same.
Rust allocates only the memory the service uses. Node and Python load an interpreter and all imported modules. Java starts a virtual machine, a JIT compiler, the Spring context and the Hibernate metamodel before the first request. The Rust service is one statically linked binary of a few megabytes.
Artifact size
The Rust binary is a few MB, but the image uses debian:bookworm-slim as base and is 116 MB. The Python image on python:3.12-slim is 243 MB. The Java and Node images are about 400 MB each. In Java most of the size is the JRE layer under the fat jar. In Node it is node_modules and the Prisma query engine.
What the benchmark does not capture
The benchmark measures the runtimes. A migration decision also depends on the systems the service uses: authentication, internal services, data contracts, observability and the deployment model. With lower integration cost, you can build a working prototype and compare it with the migration estimate.
Some technologies are chosen for their libraries. AI tools cannot generate a replacement for a mature library such as a large Apache project. For standard services, with a repository layer, an API layer and common integrations, the runtimes are now close to interchangeable, and a migration is a normal engineering trade-off.
When a migration makes sense
It makes sense when the service does not depend on libraries that exist only in one ecosystem, and the target runtime has a mature framework for the job. A REST API on a relational database with standard observability is this case. Spring Boot, FastAPI, Fastify and Axum all cover it. In this benchmark the Rust service used less CPU than Python and Node, and less memory than all the others.
It does not make sense when the service depends on libraries of one runtime. Python ML inference depends on PyTorch, NumPy or JAX, and these libraries have no production-ready equivalent outside CPython. Agent systems built on LangGraph have the same limit, because LangGraph and its integrations are Python libraries. Services with a lot of business logic are also bad candidates. Rewriting the business logic costs more than the runtime saves, and AI tools reduce the integration work, not this rewrite.
Run it yourself
The project is at github.com/ValerioMC/runtime-bench. It includes the four services, the Kubernetes manifests, the Prometheus scrape configuration and the Grafana dashboard.
You need Docker, Minikube and kubectl. One command creates the cluster, builds the images, deploys the services and starts the port-forwards:
make up
make stress then sends concurrent load to the four services for 120 seconds, with 8 workers per service. You can change the duration and the concurrency:
make stress # 120 s default
DURATION=300 CONCURRENCY=16 make stress
Grafana runs at http://localhost:3000 with user admin and password admin. Its dashboard shows CPU, memory and request throughput of the four services side by side. The charts in this article come from that dashboard.