# Turn GitHub Issues into Pull Requests for a Spring Boot App

Yes. Label a GitHub issue on a Spring Boot app and an agent reproduces it with a MockMvc test, fixes the controller and opens a pull request that closes the issue.

[Run this on Rush](https://cloud.getrush.ai/run/github-issue-to-pr/spring-boot/), in a Project that has your repository.

## What the agent does

The agent reads the issue and then the build before touching Java. In an app like spring-projects/spring-petclinic, `pom.xml` inherits `spring-boot-starter-parent 4.1.0`, sets `java.version` to 17, binds `spring-javaformat-maven-plugin` to `validate` and runs Checkstyle with the `nohttp` rules from `src/checkstyle/` in the same phase, so a formatting slip or an `http://` string fails the build before compilation. There is a parallel `build.gradle` and Gradle 9.5.1 in `gradle/wrapper/gradle-wrapper.properties`, and `.github/workflows/` runs `./mvnw -B verify` and `./gradlew build` on JDK 17 as two separate workflows. Maven is 3.9.16 through `.mvn/wrapper/maven-wrapper.properties`. The agent uses the wrappers and never a global Maven.

The code the issue names is small and structured. The `owner` package has `OwnerController`, `PetController` and `VisitController`, the `Owner`, `Pet`, `PetType` and `Visit` entities, `OwnerRepository` with `findByLastNameStartingWith(String, Pageable)`, and `PetValidator`. `OwnerController.processFindForm` strips the last name, pages five at a time through `findPaginatedForOwnersLastName`, redirects to page 1 when the page is out of range, rejects `lastName` with `notFound` when nothing matches and redirects straight to the owner when exactly one does. `PetController.processCreationForm` rejects a duplicate pet name with `duplicate` and a future birth date with `typeMismatch.birthDate`, and catches `DataIntegrityViolationException` for the database-level duplicate. `VisitController.processNewVisitForm` rejects a date that is not after today with `typeMismatch.visitDate`, and its `minVisitDate` model attribute is tomorrow. Messages for those codes live in `src/main/resources/messages/messages.properties` and the ten locale files beside it.

The reproduction is a test in the class that already covers the controller. `OwnerControllerTests`, `PetControllerTests` and `VisitControllerTests` are `@WebMvcTest` slices with a `@MockitoBean` repository and `MockMvc`, so a request like `post("/owners/{ownerId}/pets/{petId}/visits/new").param("date", LocalDate.now().toString())` with `model().attributeHasFieldErrorCode("visit", "date", "typeMismatch.visitDate")` is the shape of an issue about validation. A persistence issue needs `ClinicServiceTests` or `PetClinicIntegrationTests`, which starts the app on a random port against H2 with `database=h2` from `application.properties`; `MySqlIntegrationTests` and `PostgresIntegrationTests` use Testcontainers and are `disabledWithoutDocker`, so the agent runs them when the sandbox has Docker and says so either way. A message change is checked by `I18nPropertiesSyncTest`, which fails when a key is missing from a locale file or a template has hard-coded text outside `th:text`.

The fix stays in the layer the issue names and keeps the form flow: a validation issue is a `rejectValue` in the controller or a constraint on the entity (`Owner.telephone` is `\d{10}` with the `telephone.invalid` message), a search issue is in `processFindForm` or the repository query, a display issue is in the Thymeleaf template under `src/main/resources/templates/`. The agent runs `./mvnw spring-javaformat:apply` on the files it touched, then `./mvnw -B test -Dtest=<Class>` for the fast loop and `./mvnw -B verify` and `./gradlew build` before opening the pull request, because a change that passes one build and not the other still leaves a red check. The pull request body says `Closes #<issue>` and quotes the failing then passing test.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Fix the GitHub issue that follows this prompt in this Spring Boot 4.1 application on Java 17 with Maven and Gradle wrappers. Read pom.xml, the controller, entity, repository and Thymeleaf template the issue concerns, and the existing test class for that controller. Reproduce the reported behavior first with a test in that class: a @WebMvcTest with MockMvc for a request, binding or validation behavior, or ClinicServiceTests or PetClinicIntegrationTests when persistence is involved. Run it alone with ./mvnw -B test -Dtest=<Class> and confirm it fails. Fix the cause in the controller, entity, repository, template or messages.properties and its locale files, keeping the existing form flow and redirects. Run ./mvnw spring-javaformat:apply on the files you edited, then ./mvnw -B verify and ./gradlew build until both pass. Do not disable Checkstyle, the formatter or a test. Open a pull request whose description says Closes #<issue>, explains the cause and quotes the failing then passing test.\n\nIssue #88: Searching owners by last name with trailing spaces returns no results even though the owner exists.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request with `Closes #<issue>` in its body. The diff holds the new test method in the controller's existing test class or in an integration test, the fix in `src/main/java/org/springframework/samples/petclinic/`, and, when a message code changed, the key in `messages.properties` and each locale file so `I18nPropertiesSyncTest` stays green. The description names the cause in the code's own terms, quotes the test failing before the fix, and quotes the `./mvnw -B verify` and `./gradlew build` summaries after it.

The reviewer reads the test first and confirms it fails on `main`. Then they check the fix kept the MVC flow: the same view names, the same redirects, the same `rejectValue` codes, no `@Disabled`, and no change to `src/checkstyle/` or the formatter plugin. Both `Java CI with Maven` and `Java CI with Gradle` run on the pull request, so the reviewer sees the two builds green on GitHub rather than taking the sandbox's word.

When the issue is in a class that needs Docker and the sandbox could not run it, the run says that, with what it did run and what remains for the pull request's own CI, instead of reporting a fix it could not prove. The session keeps every Maven and Gradle command and its output.

## Run it on every GitHub issue labelled agent

In the console, add an automation to the Project with this prompt and a `github` trigger on `issues.labeled`, filtered to the `agent` label in `config.labels`. The trigger appends the issue after the prompt, so leave the example issue out of the automation's prompt. Only labels added by an owner, member or collaborator fire it, by GitHub's `author_association`. Each labelled issue gets its own sandbox, branch and pull request, and `max_runs_per_hour` bounds a triage session that labels many at once.

The same request can come from your own tracker through `POST /o/{org}/sessions`, with the issue's text after the prompt and the delivery id as the `Idempotency-Key`. Set a `webhook_url` on the key and Rush POSTs `run.completed` when the turn finishes, with the `session_url` that reads the pull request.

## Why run it on Rush?

Reproducing a Spring Boot issue means JDK 17, a cold Maven and Gradle download, a formatter that must run before the compiler, and a suite that mixes MockMvc slices with Testcontainers classes that silently skip without Docker. On a laptop that is a different Java on the path and an afternoon. A Rush run builds from the wrappers in its own sandbox, keeps every command and its output, and the pull request quotes the failing test rather than asserting there was one.

The fix is proportionate because the agent reads the controller, the validator codes and the message bundles before editing, and runs both builds because the repository has both. Several labelled issues run at once, each on its own branch, and each ends in a pull request that two GitHub workflows confirm, or in a note explaining why the issue could not be reproduced.

## Frequently asked questions

### Which JDK does it use?

Java 17, from `<java.version>` in `pom.xml` and the toolchain in `build.gradle`, which is also what both workflows set up with `actions/setup-java`. The `maven-enforcer-plugin` refuses an older JVM, so the sandbox installs 17 before running the wrapper.

### MockMvc passed. Does that prove the database change?

No. The controller tests mock `OwnerRepository` with `@MockitoBean`, so they prove binding, validation and the view, not a write. For a persistence issue the agent adds to `ClinicServiceTests` or `PetClinicIntegrationTests`, which run against H2, and the pull request labels which kind of evidence it has.

### Will it fix a formatting or nohttp failure it caused?

Yes. `spring-javaformat-maven-plugin` and Checkstyle both run in the `validate` phase, so the agent runs `spring-javaformat:apply` on the files it edited and avoids `http://` literals. It never turns either plugin off.

### What if the fix changes a user-facing message?

It adds or changes the key in `messages.properties` and in each `messages_<locale>.properties` beside it, because `I18nPropertiesSyncTest` fails when a key is missing from any locale, and it keeps template text inside `th:text` for the same test's hard-coded string check.

### Does it run the MySQL and PostgreSQL integration tests?

When the sandbox has Docker, `MySqlIntegrationTests` starts a `mysql:9.7` container through Testcontainers and `PostgresIntegrationTests` uses `docker-compose.yml`; without it they are skipped as `disabledWithoutDocker` says. The pull request states which ran, and the GitHub workflows run them again.
