Ark provides two starters for Spring Boot:
| Starter | Stack | Auto-configures |
|---|---|---|
ark-spring-boot-starter |
Spring MVC (sync) | JsonSerializer + HttpTransport + ArkClient.Builder + ArkProperties + TlsResolver |
ark-spring-boot-starter-webflux |
Spring WebFlux (reactive) | JsonSerializer + ReactorHttpTransport + ReactorArkClient.Builder + ArkWebFluxProperties + TlsResolver |
Both starters support @RegisterArkClient declarative clients, per-client configuration, TLS/SSL, trust-all, headers, interceptors, and GraalVM native image.
<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-spring-boot-starter</artifactId>
</dependency>JsonSerializer-JacksonSerializerusing Spring'sObjectMapperHttpTransport-ArkJdkSyncTransportwith defaultHttpClient(HTTP/2)ArkJdkAsyncTransport- default async transport (forAsyncArkClientand@RegisterArkClientinterfaces returningCompletableFuture<T>)ArkClient.Builder- prototype-scoped, pre-configured with serializer + transport
All beans use @ConditionalOnMissingBean - define your own to override.
@Configuration
public class HttpClientsConfig {
@Bean
public Ark oauthClient(ArkClient.Builder arkBuilder) {
return arkBuilder
.baseUrl("https://oauth.provider.com")
.build();
}
@Bean
public Ark apiClient(ArkClient.Builder arkBuilder) {
return arkBuilder
.baseUrl("https://api.myservice.com")
.requestInterceptor(req ->
req.header("Authorization", "Bearer " + tokenService.getToken()))
.build();
}
}@Service
public class UserService {
private final Ark apiClient;
public UserService(@Qualifier("apiClient") Ark apiClient) {
this.apiClient = apiClient;
}
public User findById(String id) {
return apiClient.get("/users/" + id)
.retrieve()
.body(User.class);
}
}Override the default JDK transport:
@Bean
public HttpTransport httpTransport() {
return new ArkJdkSyncTransport(HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build());
}<dependency>
<groupId>xyz.juandiii</groupId>
<artifactId>ark-spring-boot-starter-webflux</artifactId>
</dependency>JsonSerializer-JacksonSerializerusing Spring'sObjectMapperReactorHttpTransport-ArkReactorNettyTransportwith default Reactor NettyHttpClientReactorArkClient.Builder- prototype-scoped, pre-configured with serializer + transportArkWebFluxProperties- type-safe configuration (@ConfigurationProperties)TlsResolver- SSL bundle resolution (auto-detectsSslBundles)- Auto-discovery of
@RegisterArkClientinterfaces with reactive proxy creation
All beans use @ConditionalOnMissingBean - define your own to override.
@Configuration
public class HttpClientsConfig {
@Bean
public ReactorArk apiClient(ReactorArkClient.Builder arkBuilder) {
return arkBuilder
.baseUrl("https://api.example.com")
.build();
}
}@RestController
public class UserController {
private final ReactorArk client;
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable String id) {
return client.get("/users/" + id)
.retrieve()
.body(User.class);
}
}@RegisterArkClient(configKey = "user-api")
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
Mono<User> getUser(@PathVariable String id);
@GetExchange
Flux<User> listUsers();
@PostExchange
Mono<User> createUser(@RequestBody User user);
}@RestController
public class UserController {
private final UserApi userApi;
public UserController(UserApi userApi) {
this.userApi = userApi;
}
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable String id) {
return userApi.getUser(id);
}
}# application.properties
ark.logging.level=BODY
ark.client.user-api.base-url=https://api.example.com
ark.client.user-api.http-version=HTTP_2
ark.client.user-api.connect-timeout=5
ark.client.user-api.read-timeout=15
ark.client.user-api.trust-all=false
ark.client.user-api.throw-on-error=true
ark.client.user-api.headers.Authorization=Bearer ${TOKEN}
ark.client.user-api.tls-configuration-name=my-cert
# TLS bundle
spring.ssl.bundle.pem.my-cert.truststore.certificate=classpath:certs/ca.crt
⚠️ trust-all: truedisables certificate validation. Use only in local development. See Security & TLS in README.
Set
throw-on-error=falseto return HTTP 4xx/5xx responses instead of raisingApiException. See Permissive error handling.
Same configuration structure as the sync starter. See Declarative Spring Clients for full annotation details.
Note: Retry is not configured via properties for reactive clients - use Reactor's built-in
.retryWhen()instead. See Retry & Backoff.
For manual package scanning (instead of auto-discovery):
@SpringBootApplication
@EnableArkWebFluxClients(basePackages = "com.example.clients")
public class MyApplication { }The starter provides type-safe configuration via @ConfigurationProperties:
# application.properties
# Global logging level: NONE, BASIC, HEADERS, BODY
ark.logging.level=BODY
# Per-client configuration (key matches @RegisterArkClient configKey)
ark.client.user-api.base-url=https://api.example.com
ark.client.user-api.http-version=HTTP_2
ark.client.user-api.connect-timeout=5
ark.client.user-api.read-timeout=15
ark.client.user-api.tls-configuration-name=my-cert
ark.client.user-api.trust-all=false
ark.client.user-api.throw-on-error=true
ark.client.user-api.follow-redirects=true
ark.client.user-api.headers.X-Api-Key=${API_KEY}
ark.client.user-api.retry.max-attempts=3
ark.client.user-api.retry.delay=500
⚠️ trust-all: truedisables certificate validation. Use only in local development. See Security & TLS in README.
Set
throw-on-error=falseto return HTTP 4xx/5xx responses instead of raisingApiException. See Permissive error handling.
follow-redirects(defaulttrue) makes the default JDKHttpClientfollow 3xx redirects, except https to http downgrades. Set it tofalseto receive 3xx responses as-is. If you supply your ownHttpClient/HttpTransportbean, you set the redirect policy yourself (HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL)).
See Retry & Backoff for full retry configuration.
@RegisterArkClient(configKey = "user-api")
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
User getUser(@PathVariable String id);
}Properties take precedence over annotation values.
Configure SSL bundles in application.properties:
spring.ssl.bundle.pem.my-cert.truststore.certificate=classpath:certs/ca.crt
ark.client.user-api.tls-configuration-name=my-certThe starter auto-detects SslBundles and creates a TlsResolver that resolves SSL contexts from Spring's SSL bundle registry.
With @RegisterArkClient, proxy beans are auto-created:
@RegisterArkClient(configKey = "user-api", baseUrl = "${api.users.url:https://fallback.com}")
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
User getUser(@PathVariable String id);
}Just inject it:
@Service
public class UserService {
private final UserApi userApi;
public UserService(UserApi userApi) {
this.userApi = userApi;
}
}If your IDE (IntelliJ IDEA, in particular) reports Could not autowire. No beans of 'UserApi' type found. on the injection point, the bean does exist at runtime — Ark registers it dynamically via ArkClientFactoryBean, but static analysis can't see it. Add @Component on the interface to make the IDE recognize it:
@RegisterArkClient(configKey = "user-api")
@Component // ← satisfies IDE inspection; no runtime impact
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
User getUser(@PathVariable String id);
}Spring's default component scan skips interfaces, so there is no double registration — Ark's scanner is the only place the bean is registered. @Component is purely a hint for tooling.
Alternative: configure IntelliJ to recognize @RegisterArkClient as a bean producer (Settings → Editor → Inspections → Spring → Spring Core → Bean Autowiring Inspection) — one-time setup per project, no annotation changes needed.
See Declarative Spring Clients for full details.