# Add Rate Limiting to a Quarkus REST API

Yes. An agent adds SmallRye Fault Tolerance rate limits to your Quarkus REST endpoints, maps rejections to 429 with Retry-After, proves it with a test, opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/add-rate-limiting/quarkus/), in a Project that has your repository.

## What the agent does

The agent reads the service before touching the build. In a Quarkus service shaped like the `hibernate-orm-panache-quickstart` in quarkusio/quarkus-quickstarts, `pom.xml` sets `quarkus.platform.version` to 3.40.1.1, `maven.compiler.release` to 17, Surefire 3.5.4 and the compiler plugin 3.15.0, and depends on `quarkus-hibernate-orm-panache`, `quarkus-rest-jackson`, `quarkus-jdbc-postgresql`, `quarkus-junit` and `rest-assured`. `src/main/resources/application.properties` turns on SQL logging, loads `import.sql`, and configures the datasource URL, username and password only under the `%prod` profile, so dev and test rely on Quarkus Dev Services to start PostgreSQL; the agent checks that its sandbox can run containers before it runs the suite, and if it cannot, points the test profile at a PostgreSQL it starts itself. The README documents `./mvnw package` and `./mvnw quarkus:dev`.

It then reads the endpoints it will limit. `FruitEntityResource` at `entity/fruits` and `FruitRepositoryResource` at `repository/fruits` each expose `GET` for the list and a single fruit, `POST` to create, `PUT` to update and `DELETE`, with `@Transactional` on the writes. Both resources end with a nested `ErrorMapper` that implements `ExceptionMapper<Exception>`, logs the error, and returns a JSON body with `exceptionType`, `code` and `error`, using the status from a `WebApplicationException` and 500 for anything else. That mapper matters: a rate-limit rejection is not a `WebApplicationException`, so without a change it would surface as a 500. `FruitsEndpointTest` is a `@QuarkusTest` that runs the same list, delete and list sequence with RestAssured against both paths. A search of the whole quickstarts repository finds `@Retry`, `@Timeout`, `@Fallback` and `@CircuitBreaker` in `microprofile-fault-tolerance-quickstart`, which depends on `quarkus-smallrye-fault-tolerance`, and no use of `@RateLimit` anywhere.

The change uses that same extension. The agent adds `quarkus-smallrye-fault-tolerance` to `pom.xml` and annotates the `POST`, `PUT` and `DELETE` methods of both resources with SmallRye's `@RateLimit`, giving a permitted number of calls per window with the window in seconds, and leaves the two `GET` methods alone unless the prompt says otherwise. It extends the `ErrorMapper` to recognize `RateLimitException` and answer 429 with a `Retry-After` header and the same JSON shape the mapper already produces, so clients see one error format. The limits are not hard-coded in the annotations only: the agent documents the `quarkus.fault-tolerance` configuration keys that override them per method in `application.properties`, after confirming the exact key names against the extension's configuration reference for the 3.40 platform.

The proof is a test and a run. A new `@QuarkusTest` sends one more `POST` than the limit allows inside the window and asserts that the last answer is 429 with a `Retry-After` header and the JSON error body, then waits past the window and asserts a `POST` succeeds again; a second test confirms `GET` is unaffected. The agent runs `./mvnw test`, then `./mvnw package` and starts the jar to send the same burst with `curl`, keeping the status lines. It opens the pull request with the limits it chose, the configuration keys, and the test and curl output.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Add rate limiting to this Quarkus REST API. Read pom.xml, src/main/resources/application.properties, the two Fruit resources under src/main/java and FruitsEndpointTest first. Add the quarkus-smallrye-fault-tolerance extension and annotate the POST, PUT and DELETE methods of both resources with SmallRye's @RateLimit, with a limit and a window you state in the pull request. Extend the existing ErrorMapper so a RateLimitException returns 429 with a Retry-After header and the mapper's JSON shape. Document the quarkus.fault-tolerance override keys in application.properties after checking their exact names for this platform version. Add a @QuarkusTest that exceeds the limit and asserts 429, waits past the window and asserts success, and one that shows GET is unaffected. Check that the sandbox can run the PostgreSQL Dev Service before running tests. Run ./mvnw test, package the app, send a burst with curl, and open a pull request with the output.",
  "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 on its own branch that adds one dependency to `pom.xml`, adds `@RateLimit` to six resource methods, extends the `ErrorMapper` class in each resource to map `RateLimitException` to 429 with `Retry-After`, adds commented override keys to `application.properties`, and adds a test class beside `FruitsEndpointTest`. Entities, repositories and the existing test are unchanged. The description states the limit and window, quotes the `./mvnw test` summary and the `curl` status lines from the burst, and names the configuration keys with the reference it checked them against.

A reviewer checks that the `GET` methods carry no limit, that the two `ErrorMapper` classes changed the same way, and that the 429 body keeps the `exceptionType`, `code` and `error` fields clients already parse. They read the test to see that it waits past the window instead of sleeping a fixed guess, and they decide whether the chosen limit suits production or should be raised through the documented keys without a code change. The transcript holds the Maven and curl output behind the description.

## Run it on every Quarkus service without rate limits

A script on your server lists the organization's repositories with `GET /o/{org}/github`, fetches each `pom.xml`, and sends one `POST /o/{org}/sessions` with this prompt for every project that depends on `quarkus-rest` or `quarkus-resteasy` and not on `quarkus-smallrye-fault-tolerance`, naming the repository in `resources`. A `request_id` per repository makes the script safe to re-run.

Set a `webhook_url` on the API key and Rush POSTs `run.completed` when each turn finishes, with the `session_url` to read the pull request from; a `run.failed` names the service whose tests the agent could not get green.

## Why run it on Rush?

Rate limiting a Quarkus API is one annotation and then the details: the exception type the extension throws, the mapper that would otherwise turn it into a 500, the header clients expect, the configuration keys for changing the limit later, and a test that proves the window resets. The agent works through each in its own sandbox with the Dev Services database running, and your laptop never builds the project.

The pull request carries the test output and the burst of `curl` calls that hit the limit, so a reviewer reads the evidence instead of reproducing it. Several services can get the same treatment at once, each in its own sandbox and branch, with the transcript showing every Maven run.

## Frequently asked questions

### Is the limit per client or for the whole endpoint?

For the method as a whole. SmallRye's `@RateLimit` counts invocations of the annotated method regardless of caller, which protects the database behind `POST`, `PUT` and `DELETE` from any burst but does not isolate one client from another. The pull request says this plainly; a per-client limit needs a filter keyed on the caller, which is a separate run.

### Why would a rate-limited call return 500 without the mapper change?

Because both resources already register an `ExceptionMapper<Exception>` that uses the status from a `WebApplicationException` and 500 for every other exception. `RateLimitException` is not a `WebApplicationException`, so the agent adds a branch for it that returns 429 with `Retry-After` and the same JSON fields.

### Can I change the limit without a redeploy of code?

Yes. SmallRye Fault Tolerance reads per-method overrides from configuration, and the pull request documents the `quarkus.fault-tolerance` keys for each annotated method in `application.properties`, after checking the names against the configuration reference for the 3.40 platform. Set them in the environment or the properties file and the annotation values become defaults.

### Does it need Docker for the tests?

The test profile sets no datasource URL, so Quarkus Dev Services starts PostgreSQL in a container when `./mvnw test` runs. The prompt tells the agent to check for a container runtime in its sandbox first; if there is none, it starts PostgreSQL another way and points the `%test` profile at it, and says so in the pull request.
