Ir al contenido principal

Java Vibe Coding: Existing Projects

Rui Dai
Rui Dai Engineer
Compartir

Vibe Coding in Java: A Practical Existing-Project Workflow

A coding agent can make a Spring controller compile while quietly changing behavior for some users. In an existing Java project, the useful question is narrower than “Can AI build this?”: can the proposed patch satisfy the repository’s contract under its actual JDK, framework version, and tests?

For this article, we ran a small change against a pinned revision of Spring’s public REST service repository. The first implementation failed a locale-specific test; a one-line correction passed the full test class. The run shows how to use vibe coding in Java as a reviewable change loop, including what the test proves and what it leaves open.

What Vibe Coding in Java Looks Like

On a maintained repository, vibe coding starts with an instruction to inspect the project and propose one bounded change. The agent can draft code and tests. The maintainer defines the acceptance criteria, runs the build, inspects the diff, and decides whether the result belongs in the project. That is a practical Java vibe coding workflow for developers who already own a codebase.

The case here uses the complete module of Spring’s REST service sample at commit 58f5ee0. Its existing GET /greeting endpoint accepts an optional name and returns JSON such as {"id":1,"content":"Hello, World!"}. We added a deliberately small feature: shout=true uppercases the greeting. With the parameter absent or false, the original response text must stay the same. An invalid Boolean value should follow Spring’s request-binding error path.

The case here uses the complete module of Spring’s REST service sample at commit 58f5ee0

This is an experiment on a historical, version-pinned sample, not a claim about a production service or the repository’s current default branch. The current Spring guide has moved on to a newer Java baseline; using its present code while reporting results from the older commit would make the experiment impossible to reproduce as written.

Choose a Safe Change in an Existing Project

The shout option is small enough to review: one controller parameter, one conditional transformation, and focused tests. It needs no database migration, dependency, DTO change, or new endpoint. It also exposes a real Java trap: String.toUpperCase() uses the JVM’s default locale. A green test under one locale may hide a different output under another.

Before asking an AI coding assistant for Java changes, write the contract in observable terms:

  • /greeting still returns Hello, World! when both parameters are absent.
  • /greeting?name=Spring%20Community still returns Hello, Spring Community!.
  • shout=false preserves that text; shout=true returns HELLO, SPRING COMMUNITY! regardless of the JVM’s default locale.
  • An unsupported value such as shout=maybe produces the framework’s client-error response.
  • The response keeps its id and content fields. The existing counter behavior is outside this edit.

That contract gives the agent a precise target without dictating every line. It also tells the reviewer which change would require a fresh decision: altering the JSON shape, changing the meaning of name, or adding a new library.

Give the Agent Java-Specific Context

A prompt that says “add an uppercase option” omits the facts most likely to break an existing build. Read the repository first, then supply the files and constraints that govern this change.

Build Tool, Framework, and Version Constraints

At the pinned commit, complete/pom.xml uses Spring Boot 2.7.1, sets java.version to 1.8, and already declares spring-boot-starter-web and spring-boot-starter-test. The module also has a build.gradle with the Boot 2.7.1 plugin and Java 8 compatibility. Those files describe alternative builds for the same sample; do not mix Maven and Gradle instructions in one run or copy dependencies from the current main branch.

The repository’s Maven Wrapper points to Maven 3.6.3. We used ./mvnw test from complete/ under OpenJDK 1.8.0_252. The Maven Wrapper runs the project’s selected Maven distribution; Gradle projects should use their own Gradle Wrapper and declared Java toolchain. Check the repository’s version settings before suggesting syntax, framework APIs, or a new dependency. Spring Boot’s 2.7.1 reference is the relevant framework source for this run.

Architecture, Style, and Acceptance Criteria

The existing controller formats Hello, %s! and returns a Greeting object. Its test class uses @SpringBootTest, @AutoConfigureMockMvc, and MockMvc to exercise the request mapping and JSON response. Those neighboring files are enough context for a two-file change. The agent does not need permission to redesign the API or replace the project’s test style.

A copyable task prompt for this specific repository would be:

At commit 58f5ee0, inspect complete/pom.xml, complete/build.gradle, GreetingController.java, and GreetingControllerTests.java. Add an optional Boolean shout query parameter to GET /greeting. Preserve the existing response when it is absent or false. When true, uppercase the content consistently across JVM locales. Keep the id and content JSON fields and existing counter behavior. Test the default, named, explicit-false, true-under-Turkish-locale, and invalid-value paths using the repository’s MockMvc style. Do not edit build files, add dependencies, or refactor unrelated code. List intended files before editing, then report the actual command, result, and diff.

A reusable repository instruction file can hold stable build and review rules; Verdent’s AGENTS.md guide describes that role. Task-specific acceptance criteria still belong in the change request.

Generate One Small Change

The controlled run changed only GreetingController.java and GreetingControllerTests.java. The first candidate used content.toUpperCase() after formatting the greeting. That looks reasonable in an English-locale review and compiles on Java 8. It also reads the process-wide default locale, which the feature contract did not intend to make part of the API.

The final implementation kept the transformation local to the controller:

String content = String.format(template, name);
if (shout) {
    content = content.toUpperCase(Locale.ROOT);
}
return new Greeting(counter.incrementAndGet(), content);

The key line is Locale.ROOT. Oracle’s Java 8 String API explicitly warns that no-argument toUpperCase() is locale-sensitive and recommends Locale.ROOT for locale-independent strings. That matches this feature’s English, locale-neutral acceptance criterion. A product that localizes greetings for readers would need an explicit locale decision instead.

An agent may propose a different implementation. Review it against the contract: does it preserve the absent-parameter path, and does it transform only response content? A patch that rewrites Greeting, moves the counter, or changes pom.xml would expand this task without adding value.

Run Tests and Inspect the Diff

The baseline matters. Before the edit, ./mvnw test passed the sample’s two existing tests. After adding shout, the test class had five tests: the two originals plus explicit false, locale-stable true, and invalid Boolean input. The first candidate failed exactly one test:

expected: HELLO, SPRING COMMUNITY!
     was: HELLO, SPRİNG COMMUNİTY!
Tests run: 5, Failures: 1, Errors: 0

The dotted capital İ is the clue. The test temporarily set the JVM default locale to Turkish, performed a MockMvc request, and restored the prior locale in a finally block. After changing the conversion to toUpperCase(Locale.ROOT), the same ./mvnw test command reported 5 tests, 0 failures, 0 errors, BUILD SUCCESS. The invalid shout=maybe request reached Spring’s argument-conversion path and the test observed a bad-request status.

We also ran git diff --check, which exited successfully. git diff --stat showed two files changed, 36 insertions and two deletions. The Git diff check catches whitespace errors; it does not judge the API contract. Review the changed method and each assertion together. In this run, pom.xml, build.gradle, the DTO, and application entry point were untouched.

These are first-hand results from the pinned sample. MockMvc exercised the Spring request path without starting a live HTTP server. The run did not test deployment, a database, authorization, performance, or an external coding agent’s reliability. The patch was produced and corrected as a controlled editorial experiment; its failure should not be presented as an observed failure by a named commercial tool.

Debug Without Expanding the Scope

A failed test should narrow the next edit. Here, compilation passed and the response status was 200, so neither dependency resolution nor request mapping explained the failure. Only the content changed, and only under the Turkish locale. The test output pointed to Java case conversion, not to Spring serialization.

The repair changed one production expression and left the acceptance test intact. That is the important debugging discipline: preserve the independent assertion, fix the behavior it exposed, rerun the full test class, and inspect the final diff. If the agent instead proposes deleting the locale test, setting the JVM locale globally, or changing the expected JSON to contain İ, ask which requirement justifies that move. None does for this feature.

On a larger project, the same rule applies to failing database or integration tests. Record the command, failing assertion, relevant configuration, and changed files. If the repair requires a schema migration or a public contract change, stop and create a separate reviewable task.

When Vibe Coding Is the Wrong Approach

This workflow works when a maintainer can state the expected behavior and independently check the patch. It is a poor starting point for an undefined library compatibility promise, a destructive migration, a payment or access-control rule, or a repository with no reproducible baseline. In those cases, settle the contract and identify the owner before delegating implementation.

The Spring sample also has limits as evidence. Its small controller made one locale error easy to isolate. A maintained service may have validation layers, generated code, security filters, multiple modules, and CI-only integration tests. Passing five sample tests does not transfer confidence to that larger system. It demonstrates a method: pin the revision, inspect its build, add a small behavior, preserve the failing assertion, and make the diff explain the result.

FAQ

Can an AI Agent Preserve Binary Compatibility in a Java Library?

It can propose an API edit, but compiling source does not establish that previously compiled clients will still link. Compare the released artifact’s public surface with the candidate, run the project’s compatibility check, and test an old compiled consumer without rebuilding it. The Java Language Specification’s binary-compatibility rules define this separately from ordinary source compatibility. The library owner must approve exceptions.

How Should an AI Agent Handle Java Annotation Processors?

Read the processor configuration and generated-source ownership before changing a class that depends on generated code. Gradle gives processors a separate annotationProcessor configuration; Maven Compiler Plugin supports annotationProcessorPaths. Run a clean compile under the repository’s JDK. Do not hand-edit generated files or move a processor into runtime dependencies merely to silence an error. The sample experiment did not use an annotation processor.

Can Vibe Coding Preserve Spring Boot Database Migrations?

A passing controller test cannot answer this. For a project using Flyway, keep an applied versioned migration unchanged, add a new versioned migration when the schema decision is approved, and validate against a database with representative migration history. Flyway’s validation command compares applied migrations with the available names, types, and checksums. Review data migration and rollback behavior separately. The greeting sample had no database.

How Should Vibe Coding Handle Java Serialization Compatibility?

Treat persisted Java object streams as their own contract. Preserve the intended serialVersionUID, review field and class-hierarchy changes against the serialization versioning specification, and test reading representative bytes written by the previous release. Matching an identifier alone does not show that old data retains its intended meaning. A JSON response test does not cover Java object serialization.

How Should Teams Check Licenses in AI-Suggested Java Dependencies?

Require the exact artifact, version, purpose, and transitive additions. Inspect the resolved dependency tree and the distributed license metadata, then compare obligations with team policy. SPDX identifiers and expressions help normalize that record; they do not grant approval. In the experiment, the strongest license decision was simple: the feature needed no new dependency.

Conclusion

Vibe coding in Java earns its place when a small request becomes a patch whose behavior another developer can verify. In the pinned Spring sample, the original build passed two tests, the first feature patch failed one locale assertion, and the corrected two-file diff passed all five tests. That result supports the shout behavior in this test environment. It does not certify a production rollout or a particular AI tool.

For an existing project, keep the acceptance criteria outside the agent’s answer, use the repository’s actual JDK and build configuration, and review the production and test diffs together. The useful finish line is a maintainer who can explain why the change passed.

Rui Dai
Escrito porRui Dai Engineer

Hey there! I’m an engineer with experience testing, researching, and evaluating AI tools. I design experiments to assess AI model performance, benchmark large language models, and analyze multi-agent systems in real-world workflows. I’m skilled at capturing first-hand AI insights and applying them through hands-on research and experimentation, dedicated to exploring practical applications of cutting-edge AI.

Guías Relacionadas