AI-assisted development
Why AI agents write good Java
When an AI agent writes a growing share of your code, the language question changes. The best language for a coding agent is not only the one you like writing. It is also the one the model generates reliably, the one that gives the fastest signal that generated code is wrong, and the one that runs cheapest when the agent executes it in a loop. To the surprise of some, Java scores well on all three.
Write once, run for decades
A model writes the code it was trained on. Java's training material is thirty years of public code, specifications, books and answered questions, and a large part of it is written against standards, the documented and versioned interfaces of the JDK itself, Jakarta EE and MicroProfile. Writing against a standard is the safest bet, since standards do not change often. Because of Java's backward compatibility, we are in a situation where code written in 2012 usually runs on the latest version of Java, if developed against the standard API. The same holds at enterprise scale. A large codebase built on a standard API such as MicroProfile usually survives upgrades without a big migration, because the core parts keep running as they are. The famous exception is the javax to jakarta package rename, one rename in a quarter century, and that interval is the point.
Ecosystems that reinvent their APIs every second year train the same models on material that contradicts itself. That is where hallucinated methods and imports come from. The model saw four incompatible generations of the same call and blends them. Java is in better shape when it comes to that. With a small constraint, for example instructing the agent to develop against MicroProfile, you end up with quality code that compiles and runs. The training material agrees with itself, and that is why an LLM likes Java.
This familiarity works in both directions. When the model reads an enterprise codebase written against standard APIs, it understands what the code represents faster and with less context. The changes it makes land in the right places.
How do you run a single Java file without a build tool?
Since Java 25 you run a single Java file directly with the java launcher, no build tool and no project setup. An agent that writes a tool must also run and verify it, and ceremony is the cost of that loop. A minimal program is a file, not a class:
void main() {
IO.println("Hello Bosnian JUG");
}
Save it as welcome.java and run it straight from the source. There is no compile step to see:
$ java welcome.java
Hello Bosnian JUG
The feature is called compact source files and instance main methods, previewed since Java 21 and final in Java 25 as JEP 512. With module import declarations (JEP 511) the import block collapses too. One line, import module java.base, puts collections, streams, files and regular expressions in scope, and the HTTP client arrives with one more line, because it lives in its own module.
Here is an uptime check, a job that usually goes to Bash or Python. One file, no build, no dependencies. Supply chain attacks have made zero dependencies a goal in itself:
import module java.base;
import module java.net.http;
void main(String... args) {
var client = HttpClient.newHttpClient();
for (var url : args) {
var started = System.nanoTime();
try {
var response = client.send(
HttpRequest.newBuilder(URI.create(url)).build(),
HttpResponse.BodyHandlers.discarding());
var ms = (System.nanoTime() - started) / 1_000_000;
IO.println(response.statusCode() + " " + ms + " ms " + url);
} catch (Exception e) {
IO.println("DOWN " + url);
}
}
}
Save it as check.java and run it with the same command as the minimal example. Arguments pass straight through:
$ java check.java https://jug.ba https://example.com
200 450 ms https://jug.ba
200 403 ms https://example.com
Bash is a good option, especially for smaller scripts, and unbeatable for a one-liner. The script above is roughly where Bash stops being kind. Past that size Bash fails quietly, through word splitting, unquoted variables and exit codes nobody checked. The Java version is typed, its failures are exceptions that name the line, and the standard library replaces a chain of external commands. And when a complex tool ships as a script, far more developers can review Java than advanced Bash.
For an agent, this is the cheapest loop there is. Generate the file, run the file. No build file to hallucinate, no dependency resolution to break, no version conflicts to explain.
When a script outgrows java.base, there is a step before a build tool. JBang runs the same kind of single file and resolves dependencies declared in a comment at the top of it, which suits an agent for the same reason the plain launcher does. The whole program stays one generatable file, dependencies included.
This needs JDK 25. Between Java 21 and 24 the feature exists only as a preview, behind preview flags.
From scripts to services
The same properties carry into enterprise code. Ask an agent for an endpoint developed against the standard API, shaped as a thin boundary in front of two controls, and this is what comes back:
@Path("subscriptions")
@ApplicationScoped
public class SubscriptionsResource {
@Inject
SubscriptionCreator creator;
@Inject
SubscriptionFinder finder;
@GET
@Path("{email}")
public Response subscription(@PathParam("email") String email) {
return this.finder
.subscription(email)
.map(Responses::ok)
.orElseGet(Responses::noContent);
}
@POST
public Response subscribe(Subscription subscription) {
var result = this.creator.subscribe(subscription);
return switch (result) {
case Created created -> Responses.created(created);
case AlreadyExists exists -> Responses.conflict(exists);
case Invalid invalid -> Responses.invalid(invalid);
};
}
}
The wiring is the standard, jakarta.*, and the shape is boundary-control-entity. The boundary stays thin and delegates to two controls, and the lookup maps an Optional straight onto a response. The creation result is a sealed type, so the switch must map every outcome or the class does not compile. Add a fourth outcome and every boundary that forgot it fails the build, which is exactly the property you want when an agent writes the next change. And the class is not tied to a runtime. The same code runs on any server that speaks the standard.
The compiler reviews the output
Agent-written code needs verification more than human-written code, because nobody watched it being written. Static types make the first review free. A wrong name, a wrong type, or a missing argument fails at compile time, in milliseconds, with a message that names the line. An agent acts on that signal mechanically, and the loop converges.
A dynamically typed script gives no such signal. It runs until the wrong line executes. In Java, an entire class of the agent's mistakes cannot survive to review, because they cannot compile. And when the agent generates against standard APIs, the review gets easier too, since the reviewer reads types and annotations they already know. One rule holds regardless of language. Every line an agent writes gets reviewed and owned by a human.
Faster is greener
Agents run code in loops, and at that scale runtime cost is real money. The best-known measurements, first published by Pereira et al. at SLE 2017 and extended in a 2021 journal version, rank 27 languages by energy, time and memory across the Computer Language Benchmarks Game. Here is the neighborhood that matters, normalized to C, with memory normalized to Pascal as the study's most frugal language, and each value's rank among the 27 in parentheses:
| Language | Energy | Time | Memory |
|---|---|---|---|
| C | 1.00 (1) | 1.00 (1) | 1.17 (3) |
| Rust | 1.03 (2) | 1.04 (2) | 1.54 (7) |
| C++ | 1.34 (3) | 1.56 (3) | 1.34 (5) |
| Java | 1.98 (5) | 1.89 (5) | 6.01 (22) |
| C# | 3.14 (13) | 3.14 (10) | 2.85 (14) |
| Go | 3.23 (14) | 2.83 (7) | 1.05 (2) |
| JavaScript | 4.45 (17) | 6.52 (16) | 4.59 (20) |
| Python | 75.88 (26) | 71.90 (26) | 2.80 (12) |
On energy and time Java sits with C, C++ and Rust, a factor of 38 ahead of Python on both. A 2024 re-examination tightened the picture further, twice in Java's favor. It found that energy consumption is directly proportional to runtime whatever the language, so the fast language is the frugal one. And it traced Java's numbers to JIT warmup on short-lived benchmarks, a measurement artifact rather than a property of the language, with the gap to C shrinking substantially at steady state.
Memory is Java's weak column, 6.01 in that table, and it is also where the data aged most. Java 25 ships compact object headers (JEP 519), one flag today and the default from JDK 27, which shrink every object's header from twelve to eight bytes. And where memory and startup dominate, GraalVM native image compiles the program ahead of time into a binary. The binary starts in milliseconds and holds a fraction of the JVM's resident memory, which is why runtimes like Quarkus and Helidon offer it as a build target.
Where Python still wins
Ask an agent for a script and name no language, and you usually get Python. For many tools a Python library already exists and is commonly used, and in the machine learning world it is the go-to language.
The default also cuts the other way. The same agent that wrote a Python tool can port it to Java on request, and the table above prices what that buys, the same job faster and greener, most visibly where the work is CPU-bound. A rewrite like that used to be a rare side project that nobody funded, and now it is an instruction and a review.
Set the target once
Thirty years of public Java code holds every era's idioms, so you tell the agent which to use. A skill states the target, Java 25 or 21, and the do's and don'ts, whether that is records, streams instead of loops, or naming conventions. Write the skill once and every future request follows it. It is the same mechanism as the style guide we packaged as an agent skill, pointed at code instead of prose.
Java 25 is a long-term support release, so none of this is a moving target. The runtimes and the tooling will sit on 25 for years. Java has been getting better year after year, and the areas named as problems a decade ago, the ceremony, the startup, the memory, are the areas that improved the most.