Build1 distinct publisher3 min readUpdated
An intern's slice of a legacy Java modernization project found the bottleneck is not generation. It is having a test that can tell you when the model is actually finished.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
An intern working on Pulsar Modernizer, a project meant to turn legacy Java into modern Spring Boot code, published what happened when he moved from the understanding half of the pipeline to the generating half [1]. His extraction component was already respectable, at F1 = 0.857 on an annotated corpus with a React front end running in Docker [2], and the write-up is useful precisely because the generation step is where the project stopped being a measurement problem and became a governance one [3].
The naive version failed inside an hour, and it failed in three ways worth separating [4]. Hallucination is the boring one: invented helper classes, method calls with wrong signatures, all of it caught by a compiler. The other two are not tooling problems. There is no stopping criterion, because the model announcing that it is done is not evidence that it is done [4]. And there is no equivalence criterion, because code that compiles is not code that behaves like the system it replaced [4].
The architecture he settled on is the correct shape for that problem. An APIDesigner agent emits a JSON contract with classes, methods, DTOs and declared exceptions; a CodeGenerator writes the .java; a TestGenerator writes the *Test.java; a verifier runs mvn test [5]. The load-bearing decision is that every rule extracted from the legacy code becomes a test the new code must pass, so the authority moves from the model to javac and JUnit [6]. He ran the whole thing on Qwen 2.5 Coder 3B via Ollama on a laptop, on the argument that a pipeline requiring GPT-4-level intelligence to hold together will not ship inside a bank [7].
Then the oracle bit back. He hand-wrote JUnit oracles for four rules from a banking corpus and got 2 of 3 green on a raw prompt and 3 of 3 after adding three conventions to the top of the prompt [8]. The conventions: null inputs are always rejected rather than silently ignored, "cannot exceed N" means N passes and N plus epsilon fails, and currency and country lists are case-sensitive [9]. Every failure he observed traced to one of those three, which means the defects were in his own rule annotations, not the model: grammatical English with no written tiebreaker, and a fluent model picking the other branch [10]. That is the finding operators should copy. A generation pipeline is an ambiguity detector aimed at your specification, and most legacy specifications have never been forced to disambiguate.
The repair loop is the cautionary half. Feed the compiler error back, up to five iterations [11]. On a simple currency-allowlist rule the model called .toUpperCase() on null, crashed at runtime, and fixed itself on the second pass [12]. On a velocity rule, more than 20 transactions in a sliding hour adding +30 to a fraud score [13], round one emitted a null check against a Java long primitive, javac refused, and rounds two through five returned character-identical code and the identical error [14]. Four of the five permitted iterations, 80 percent of the budget, produced no change at all [1]. His diagnosis is that the repair prompt was Markov-1, carrying only the previous attempt, so it sat on a fixed point [15]. A loop terminating tells you nothing about what it terminated on [16].
Two things to hold loosely. The author says outright this is a slice over four rules in a language with parsers everywhere, on a corpus he annotated himself, and that the results are directional rather than statistical [17]. And the reported tally is out of three while the oracle count is four, so one rule's result is unaccounted for in the numbers as published [2].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Pulsar Modernizer was intended to turn legacy Java into modern Spring Boot code; the author's assigned part was the 'understand the old code' component, an NLP pipeline extracting intentions and rules from legacy Java, and his supervisor asked what came after that.
The extraction component scored F1 = 0.857 on the annotated corpus, with a React UI, running in Docker.
The author wrote in his notes that nobody had actually tried the generation part and that everyone assumed it would be easy because LLMs, which he called obviously wrong.
Feeding the old code and extracted rules to an LLM with an instruction to modernize hit three problems in the first hour: the model hallucinates, inventing helper classes that do not exist and calling methods with the wrong signature; there is no criterion for stopping, because the model says it is done and there is no test to confirm it; and there is no criterion for equivalence, because even code that compiles is not known to do what the old code did.
The author built three specialized agents in sequence: an APIDesigner producing a JSON contract (class, methods, DTOs, throws), a CodeGenerator producing .java files, and a TestGenerator producing *Test.java files, both feeding a verifier that runs mvn test.
The stated key insight was that each rule extracted from the legacy code should become a test the generated code has to pass; the author says he does not trust the LLM, he trusts javac and JUnit.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Single self-reported prototype, n=4 rules
All claims rest on one first-person write-up with no independent replication, no third-party measurement and no released dataset. The experimental base is four rules the author annotated himself, one small local model, and one language chosen for its mature tooling; the author says so explicitly. What raises this above the floor is the specificity and falsifiability of the mechanics reported — exact compiler failure (long versus Long), exact iteration counts, exact prompt delta, and named artifacts — plus the fact that the negative result is reported against the author's own interest. An unreconciled denominator gap (four oracles, pass rates out of three) keeps the score low.
One local prototype, no external users
Observed usage is confined to the author's own machine: a local Qwen 2.5 Coder 3B stack via Ollama, hand-written oracles over four banking rules, and a single end-to-end demo run against Spring PetClinic producing four classes in roughly ten minutes. No deployment beyond the internship, no external adopters, no released package, and the author states this is not a production system. The non-zero score reflects that the plumbing demonstrably ran end to end on a public repository rather than existing only as a design sketch.
Mildly understated; failure reported against interest
The write-up leads with a negative result — a repair loop that converged on code javac rejected — attributes the failures to the author's own annotations rather than to the model, and closes with an explicit limitations paragraph and a list of things he got wrong. That is calibrated to slightly conservative relative to the evidence presented. The only inflationary pressure is generalization pressure in the framing: a four-rule slice on one 3B model is presented as a lesson about LLM code modernization broadly, and self-reported metrics (F1 = 0.857, the oracle tallies) are stated without qualification of measurement method. Net effect is close to aligned, tipping slightly negative.
Career-visibility incentive, no vendor stake
The author is an intern publishing his own project on a developer-community platform, which creates a visible reputational and hiring incentive to make the work look competent and quotable. Countervailing factors are substantial: no vendor, sponsor, commercial product or funding is disclosed anywhere in the source; the stack chosen is an open local model rather than any paid API he might be promoting; and the piece's central content is a self-critical failure plus a list of his own mistakes, which is a poor fit for pure self-promotion. Distortion pressure therefore exists but is modest and disclosed by construction.
Mechanics credible, generality unestablished
Confidence is moderate. The narrow mechanical claims — the javac long/Long rejection, the identical rounds 2 through 5, the three-convention prompt delta, the local 3B stack — are internally coherent, specific and reported against the author's interest, so they are likely accurate as described. Confidence drops sharply for anything general: a single publisher, a single source, four self-annotated rules, one small model, no ablation isolating the Markov-1 prompt from model capacity, and an unreconciled four-versus-three tally. The claim that the stopping problem is the real bottleneck is plausible and well argued but not established by this evidence.
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
build
The proxy in your call path decides whether @Transactional does anything at all1 distinct publisher
build
A RAG stack lived seven hours before a hosted embedding endpoint returned 4041 distinct publisher
build
The payout trigger is a balance-sheet decision, not a timestamp you happen to have1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 20, 2026