One client model that survives framework changes, transport changes, and execution-model changes.
Write fluent or declarative code; pick JDK / Apache / Reactor Netty / Vert.x under the
hood; run sync / async / reactive; host on Spring Boot, Quarkus, or a plain main().
Compose retry, metrics, or your own behavior through transport.with(...) decorators.
Quick Start • Frameworks • Execution Models • Architecture • Documentation
Ark is an HTTP client toolkit for Java that separates the concerns other clients bundle: how you write requests (fluent vs declarative), how they're sent (which transport), how they're serialized (which JSON library), how they execute (sync / async / reactive), and where they run (Spring / Quarkus / standalone). Each axis is pluggable - the rest stays the same.
Core capabilities:
- 🧩 Fluent + declarative - the
Arkbuilder API or@RegisterArkClientinterfaces with@HttpExchange/ JAX-RS annotations - 🚢 Pluggable transports - JDK HttpClient, Apache HC5, Reactor Netty, Vert.x WebClient - swap without changing call sites
- 🔌 Composable decorators -
transport.with(Retry.of(...))chains retry, your metrics, your tracing - same pattern everywhere - ⚡ Five execution models - sync, async (
CompletableFuture), Reactor (Mono/Flux), Mutiny (Uni/Multi), Vert.x (Future) - one API shape - 🍃 Any host - Spring Boot MVC, Spring Boot WebFlux, Quarkus (JVM + native), or standalone - one client model
- 🛡️ Permissive error handling -
.noThrow()per request orark.client.<name>.throw-on-error=false- inspect 4xx/5xx without exceptions - 🔍 Raw response access -
.raw()on every*ClientResponseor declareRawResponseas a proxy return type - bypass deserialization when needed - ⚙️ GraalVM native - reflection / proxy hints emitted automatically for both Spring Boot AOT and Quarkus build-time
- 📊 Verified compat - upstream Spring Boot / Quarkus patches tested weekly via CI matrix
1. Add Ark - import the BOM and a host starter (Spring shown; other frameworks below):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-bom</artifactId>
<version>1.0.8</version> <!-- ark-bom -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-spring-boot-starter</artifactId>
</dependency>2. Write a declarative client:
@RegisterArkClient(configKey = "users-api")
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
User getUser(@PathVariable String id);
@PostExchange
User createUser(@RequestBody User user);
}ark.client.users-api.base-url=https://api.example.com
ark.client.users-api.connect-timeout=53. Inject and call it:
@Service
public class UserService {
private final UserApi api;
UserService(UserApi api) { this.api = api; }
public User find(String id) { return api.getUser(id); }
}Or use the fluent API directly — inject ArkClient.Builder (auto-configured by the starter):
@Service
public class UserService {
private final Ark client;
UserService(ArkClient.Builder builder) {
this.client = builder.baseUrl("https://api.example.com").build();
}
public User find(String id) {
return client.get("/users/" + id)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(User.class);
}
}The same model runs on every supported host - only the registration differs.
All coordinates under groupId xyz.juandiii, versioned by the BOM. Pick a host:
Spring Boot (sync / MVC)
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-spring-boot-starter</artifactId>
</dependency>Annotate interfaces with @RegisterArkClient + Spring's @HttpExchange family. The starter
auto-configures Ark, AsyncArk, and the proxy factory; AOT hints are emitted for native image.
Spring Boot (reactive / WebFlux)
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-spring-boot-starter-webflux</artifactId>
</dependency>Proxy methods return Mono<T> / Flux<T>; transport is Reactor Netty by default. Same
@RegisterArkClient interfaces work — just declare reactive return types.
Quarkus (JVM + native)
Slim (JDK transport only):
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-quarkus-jackson</artifactId>
</dependency>With Vert.x Mutiny transport (adds Uni<T> / Multi<T> proxy method support):
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-quarkus-jackson-vertx</artifactId>
</dependency>Annotate interfaces with @RegisterArkClient + JAX-RS (@Path / @GET / @POST) — or use
Spring's @HttpExchange if you prefer. Build-time reflection + proxy hints emitted for native.
Plain main()
Ark client = ArkClient.builder()
.serializer(new JacksonSerializer(new ObjectMapper()))
.transport(new ArkJdkSyncTransport(HttpClient.newHttpClient()))
.baseUrl("https://api.example.com")
.build();ark-core has zero framework dependencies — assemble it yourself.
The same @RegisterArkClient interface works across all five execution models. Pick by return type:
| Model | Module | Return type | Builder |
|---|---|---|---|
| Sync | ark-core |
T, ArkResponse<T>, RawResponse |
ArkClient.builder() |
| Async | ark-async |
CompletableFuture<T> |
AsyncArkClient.builder() |
| Reactor | ark-reactor |
Mono<T> / Flux<T> |
ReactorArkClient.builder() |
| Mutiny | ark-mutiny |
Uni<T> / Multi<T> |
MutinyArkClient.builder() |
| Vert.x | ark-vertx |
io.vertx.core.Future<T> |
VertxArkClient.builder() |
@RegisterArkClient(configKey = "users-api")
public interface UserApi {
@GetExchange("/{id}") User getUserSync(String id);
@GetExchange("/{id}") CompletableFuture<User> getUserAsync(String id);
@GetExchange("/{id}") Mono<User> getUserReactive(String id);
@GetExchange("/{id}") RawResponse getUserRaw(String id); // bypass deserialization
}| Ark | Apache HC5 | OkHttp | Spring RestClient | OpenFeign | |
|---|---|---|---|---|---|
| Fluent API | ✅ | ✅ | ✅ | ❌ | |
| Declarative interfaces | ✅ | ❌ | ❌ | ✅ (via @HttpExchange) |
✅ |
| Same interface across sync + async + reactive | ✅ | ❌ | ❌ | ❌ (RestClient vs WebClient) | partial |
| Pluggable transports without code changes | ✅ | ❌ | ❌ | ClientHttpRequestFactory) |
|
Decorator chain (.with(...)) |
✅ | ❌ | partial (interceptors) | ❌ | partial |
| Spring + Quarkus + standalone host | ✅ | standalone | standalone | Spring only | Spring only |
| Permissive error handling | ✅ | ✅ (manual) | ✅ (default) | partial (onStatus) |
partial (ErrorDecoder) |
| GraalVM native | ✅ | partial | ✅ | partial |
ark-core is plain Java with zero framework dependency. Hosts (Spring, Quarkus) plug in through
thin SPIs: JsonSerializer, HttpTransport, RequestInterceptor. Decorators stack via
transport.with(...) regardless of execution model.
flowchart LR
code([Your code]):::io
subgraph API["API surface"]
direction TB
FLUENT["Fluent builder<br/><small>client.get().retrieve()</small>"]:::surface
DECL["@RegisterArkClient<br/><small>interface-driven</small>"]:::surface
end
subgraph CORE["ark-core pipeline — same model across hosts"]
direction LR
BUILD["Build & encode"]:::core
DECO["Decorator chain<br/><small>retry, your own</small>"]:::core
VAL["Validate & decode<br/><small>or skip via .noThrow()</small>"]:::core
end
transport([Transport · JDK · Apache · Netty · Vert.x]):::io
code --> FLUENT
code --> DECL
FLUENT --> BUILD
DECL --> BUILD
BUILD --> DECO
DECO <-->|HTTP| transport
DECO --> VAL --> code
classDef surface fill:#e8f0fe,stroke:#4285f4,color:#202124;
classDef core fill:#e6f4ea,stroke:#34a853,color:#202124;
classDef io fill:#f1f3f4,stroke:#9aa0a6,color:#202124;
The full nine-axis breakdown — 19 modules under core/, execution-models/, transports/,
serializers/, proxies/, starters/, extensions/ — is in docs/design.md.
- Getting Started - fluent + declarative basics
- Sync / Async / Reactor / Mutiny - per-execution-model guides
- Transport Model - bridge pattern + decorator chain
- Spring Boot Integration - starter + properties + AOT
- Declarative Spring -
@RegisterArkClient+@HttpExchange - Quarkus - extension + native image
- Retry & Backoff - decorator-based retry with per-model strategies
- Logging -
LoggingInterceptorlevels and redaction - Compatibility Matrix - supported Spring Boot / Quarkus / Java versions
docs/design.md- architecture, SPIs, and the module layout
- Modular layout (19 modules grouped under semantic subdirectories)
- Composable transport decorators (
transport.with(Retry.of(...))) per execution model - Permissive error handling — per-request
.noThrow()+ client-levelthrow-on-errorproperty - Raw response access —
.raw()fluent +RawResponseas proxy return type - Weekly upstream compat sweep (Spring Boot / Quarkus latest patches via CI matrix)
-
RawResponsereturn type for Vert.x proxies (handler stub pending) - Observability decorators — OpenTelemetry tracing, Micrometer metrics
- Spring Cloud
@RefreshScopesupport for hot-reload of@RegisterArkClientconfigs - Tests for
ark-spring-boot-starter*andark-quarkus-jackson(plan 012)
Apache 2.0. See LICENSE.