DEV Community

Cover image for Stop Writing REST Client Boilerplate in Java
Shrinivas Shukla
Shrinivas Shukla

Posted on AI-assisted

Stop Writing REST Client Boilerplate in Java

Every Java project ends up with the same file eventually. You know the one — ApiClient.java, HttpUtil.java, SomeServiceClient.java. It opens an HttpClient, builds a request, sets a few headers, fires it off, checks the status code by hand, and manually deserializes the response body. Then you copy-paste that same shape for the next endpoint. And the next.

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users/" + id))
    .header("Accept", "application/json")
    .GET()
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
    throw new RuntimeException("Request failed: " + response.statusCode());
}
User user = objectMapper.readValue(response.body(), User.class);
Enter fullscreen mode Exit fullscreen mode

Multiply that by every endpoint you call, add retry logic by hand because the network is unreliable, and you've got a few hundred lines of code that do nothing interesting.

I got tired of writing this, so I built REST-in-peace (RIP) — a declarative REST client for Java. You describe the API as an interface, and RIP gives you back a working implementation.

@RestClient
@BaseUrl("https://api.example.com")
public interface UserApi {

    @GET("/users/{id}")
    User getUser(@PathParam("id") String id);
}
Enter fullscreen mode Exit fullscreen mode
UserApi userApi = RIP.getClient(UserApi.class);
User user = userApi.getUser("42");
Enter fullscreen mode Exit fullscreen mode

That's the whole thing. No HttpClient setup, no manual JSON parsing, no status-code checking. Here's what it actually looks like running:

Declaring a UserApi interface and calling it with RIP.getClient

It's not just another Retrofit clone

If you've used Retrofit or Feign, the interface-based idea will feel familiar — annotate a method, get an HTTP call. The part I actually built RIP around is what happens underneath that interface.

Compile-time code generation, with a reflective fallback. By default, RIP implements your interface at compile time via an annotation processor — you get a real generated class, not a runtime-built dynamic proxy. That means no reflection on your hot path, and no proxy-generation cost at startup. If you'd rather skip the build-time step, the reflective proxy is still there and works identically.

GraalVM native-image just works. Because the generated implementation is real compiled bytecode instead of a runtime proxy, there's no reflect-config JSON to hand-write for RIP's own client-generation path. Build a native image the normal way and it works.

Resilience is built in, not bolted on. Retry with backoff, a circuit breaker, a bulkhead, idempotency keys for safe retries on non-idempotent-looking calls — all as annotations on the interface, not a separate library you have to wire in yourself.

@RestClient
@BaseUrl("https://api.example.com")
public interface OrderApi {

    @GET("/orders/{id}")
    @Retry(times = 3)
    Order getOrder(@PathParam("id") String id);
}
Enter fullscreen mode Exit fullscreen mode

Response caching with stale-while-revalidate and negative caching, out of the box, configurable per client or per call.

A pluggable interceptor chain for logging, auth headers, request signing, or short-circuiting a call entirely (handy for feature flags or local overrides).

A Spring Boot starter, if that's your world — annotate the interface, inject it like any other bean, zero hand-written @Bean methods.

Reactive support via rest-in-peace-reactor — any method returning Mono<T> or Flux<T> just works once you register the call adapter.

An OpenAPI-to-@RestClient generator, so if you already have a spec, you don't have to hand-write the interface at all.

A real local test server (MockRestServer) for integration-style tests, plus a JUnit 5 extension, so you're not mocking HttpClient directly.

How it compares

REST-in-peace vs. Retrofit, Feign/OpenFeign, and RestTemplate/WebClient

Try it

<dependency>
    <groupId>io.github.shrinivas93</groupId>
    <artifactId>rest-in-peace</artifactId>
    <version>1.0.0.63</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
implementation("io.github.shrinivas93:rest-in-peace:1.0.0.63")
Enter fullscreen mode Exit fullscreen mode

It's on Maven Central, MIT licensed, and works from Java, Kotlin, or any other JVM language that can implement a Java interface.

If you try it and something's confusing, missing, or just annoying, I'd genuinely like to hear about it — open an issue or start a discussion.

Top comments (0)