agentsclimarketplace

Kora http client

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-http-client

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

Install
npx -y skills add kora-projects/kora-skills --skill kora-http-client

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Declarative Kora HTTP clients - @HttpClient interface with @HttpRoute, parameter mapping (@Path/@Query/@Header/@Cookie), @Json bodies, HttpResponseEntity, @Mapping and @ResponseCodeMapper, @InterceptWith interceptors, and OkHttp/AsyncHttpClient/JDK transports. Use when building a typed outbound HTTP client in a Kora service, wiring auth interceptors (Basic/ApiKey/Bearer), configuring per-client timeouts and telemetry under httpClient.*, or handling HttpClientResponseException. Not for OpenAPI-generated clients (use kora-openapi-generator-client) or for the inbound @HttpController server (use kora-http-server).

SKILL.md

12.9 KB, as published. Nobody here has run it

Kora HTTP Client

Declarative, compile-time HTTP clients: annotate an interface with @HttpClient, declare methods with @HttpRoute, and the annotation processor generates the implementation. No reflection, no runtime proxies. Inject the client interface like any other Kora component.

Dependencies: http-client-ok (transport) + json-module (for @Json) + annotation-processors.

All Kora artifacts inherit their version from the ru.tinkoff.kora:kora-parent BOM (the examples pin 1.2.17). Never version individual ru.tinkoff.kora:* artifacts.


Quick Start

1. Dependencies (Java)

dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")

    annotationProcessor "ru.tinkoff.kora:annotation-processors"

    implementation "ru.tinkoff.kora:http-client-ok"
    implementation "ru.tinkoff.kora:json-module"
    implementation "ru.tinkoff.kora:config-hocon"
}

For Kotlin, use ksp "ru.tinkoff.kora:symbol-processors" instead of annotationProcessor.

2. Plug the transport module into @KoraApp

@KoraApp
public interface Application extends
        HoconConfigModule,
        JsonModule,
        OkHttpClientModule {
}

3. Declare the client interface

@HttpClient(configPath = "httpClient.userApi")
public interface UserApiClient {

    @HttpRoute(method = HttpMethod.GET, path = "/users/{userId}")
    @Json
    UserResponse getUser(@Path String userId);

    @HttpRoute(method = HttpMethod.GET, path = "/users")
    @Json
    List<UserResponse> listUsers(@Query("page") int page, @Query("size") int size);

    @HttpRoute(method = HttpMethod.POST, path = "/users")
    @Json
    HttpResponseEntity<UserResponse> createUser(@Json CreateUserRequest request);

    @HttpRoute(method = HttpMethod.DELETE, path = "/users/{userId}")
    void deleteUser(@Path String userId);
}

4. Configuration (HOCON)

The client resolves config at httpClient.{lower-case class name} by default, or at the path given in configPath. The url key is required.

httpClient {
  userApi {
    url = "http://localhost:8080"
    url = ${?USER_API_URL}
    requestTimeout = "10s"
  }
}

5. Inject and use

@Component
public final class UserService {

    private final UserApiClient client;

    public UserService(UserApiClient client) {
        this.client = client;
    }

    public UserResponse getUser(String id) {
        return client.getUser(id);
    }
}

What's in references/ and assets/

FilePurpose
references/declarative-client-reference.md@HttpClient, @HttpRoute, parameter & body mapping, @Mapping, @ResponseCodeMapper, signatures, per-client/per-method config
references/async-client-reference.mdCompletionStage, Project Reactor Mono, Kotlin suspend, AsyncHttpClient/JDK transports
references/error-handling-guide.mdHttpClientResponseException, HttpClientDecoderException, HttpResponseEntity, status-aware decoding
references/interceptors-reference.mdHttpClientInterceptor, @InterceptWith, built-in Basic/ApiKey/Bearer auth, resilience
references/okhttp-transport-reference.mdOkHttp config keys, HTTP versions, proxy, OkHttpConfigurer, telemetry
assets/UserApiClient.java.templateBase CRUD client (Java)
assets/UserApiClient.kt.templateBase CRUD client (Kotlin)
assets/ResilientApiClient.java.templateClient with @Retry/@CircuitBreaker/@Timeout/@Fallback
assets/AsyncApiClient.java.templateAsync client returning CompletionStage
assets/CustomMapperClient.java.template@Mapping request body + @ResponseCodeMapper
assets/ApiKeyAuthInterceptor.java.templateCustom HttpClientInterceptor for API key auth

When to use vs NOT

Use this skill when:

  • Building a typed outbound HTTP client interface with @HttpClient + @HttpRoute.
  • Mapping parameters via @Path, @Query, @Header, @Cookie, @Json.
  • Adding @InterceptWith interceptors for auth, logging, or tracing.
  • Configuring per-client timeouts, proxy, HTTP version, or telemetry under httpClient.*.

Do NOT use when:

  • You have an OpenAPI contract and want a generated client - use kora-openapi-generator-client.
  • You need the inbound HTTP server (@HttpController) - use kora-http-server.
  • You need only the raw imperative HttpClient.execute(request) API - that is covered briefly below and in full in the docs.

Core patterns

Parameter mapping

AnnotationImportExample
@Pathru.tinkoff.kora.http.common.annotation.Path@Path String id -> /users/{id}
@Queryru.tinkoff.kora.http.common.annotation.Query@Query("page") int p -> ?page=1
@Headerru.tinkoff.kora.http.common.annotation.Header@Header("X-Trace-ID") String tid
@Cookieru.tinkoff.kora.http.common.annotation.Cookie@Cookie("sessionId") String sid
@Jsonru.tinkoff.kora.json.common.annotation.Json@Json CreateUserRequest req
@Mappingru.tinkoff.kora.common.Mapping@Mapping(TextMapper.class) Body b

The parameter name defaults to the method argument name; override it with value (e.g. @Path("userId")). Arguments are required by default; mark @Nullable to make them optional.

Response handling

By default the response is decoded for 2xx status codes; any other status throws HttpClientResponseException (carrying code(), the body, and headers). To inspect the status and headers yourself, return HttpResponseEntity<T>:

@HttpRoute(method = HttpMethod.GET, path = "/users/{userId}")
@Json
HttpResponseEntity<UserResponse> getUser(@Path String userId);

// In the caller:
HttpResponseEntity<UserResponse> response = client.getUser("123");
if (response.code() == 200) {
    return response.body();
}

HttpResponseEntity exposes code(), body(), and headers(). See error-handling-guide.

JSON bodies

@Json on a parameter writes it as JSON; @Json on the method reads the response as JSON. The json-module dependency is required.

@HttpRoute(method = HttpMethod.POST, path = "/users")
@Json
UserResponse createUser(@Json CreateUserRequest request);

@Json
record CreateUserRequest(String email, String name) {}

@Json
record UserResponse(String id, String email, String name) {}

Interceptors

Implement HttpClientInterceptor and attach it with @InterceptWith on the interface (client-wide) or a single method. Mutate the request via request.toBuilder() - mutating in place has no effect.

@Component
public final class ApiKeyAuthInterceptor implements HttpClientInterceptor {

    private final ApiKeyAuthConfig config;

    public ApiKeyAuthInterceptor(ApiKeyAuthConfig config) {
        this.config = config;
    }

    @Override
    public CompletionStage<HttpClientResponse> processRequest(
            Context ctx, InterceptChain chain, HttpClientRequest request) throws Exception {
        var authorized = request.toBuilder()
                .header("Authorization", config.value())
                .build();
        return chain.process(ctx, authorized);
    }
}

@InterceptWith(ApiKeyAuthInterceptor.class)
@HttpClient(configPath = "httpClient.dataApi")
public interface DataApiClient { }

@InterceptWith imports from ru.tinkoff.kora.http.common.annotation.InterceptWith. Kora also ships built-in auth interceptors (BasicAuthHttpClientInterceptor, ApiKeyHttpClientInterceptor, BearerAuthHttpClientInterceptor) - see interceptors-reference.

Async signatures

Methods may return T, CompletionStage<T>, or (with reactor-core) Mono<T>. In Kotlin a method may be suspend (requires kotlinx-coroutines-core).

@HttpRoute(method = HttpMethod.GET, path = "/items/{id}")
@Json
CompletionStage<ItemResponse> getItemAsync(@Path String id);

See async-client-reference.

Resilience

Resilience comes from the separate resilient-kora module (ResilientModule), not from the HTTP client itself. Its aspect annotations take a single config name; the actual values live under resilient.* in config.

implementation "ru.tinkoff.kora:resilient-kora"
@HttpClient(configPath = "httpClient.itemApi")
public interface ItemApiClient {

    @Retry("itemApi")
    @HttpRoute(method = HttpMethod.GET, path = "/items/{id}")
    @Json
    ItemResponse getItem(@Path String id);

    @CircuitBreaker("itemApi")
    @HttpRoute(method = HttpMethod.POST, path = "/items")
    @Json
    ItemResponse createItem(@Json CreateItemRequest request);

    @Timeout("itemApi")
    @Fallback(value = "itemApi", method = "listItemsFallback()")
    @HttpRoute(method = HttpMethod.GET, path = "/items")
    @Json
    List<ItemResponse> listItems();

    default List<ItemResponse> listItemsFallback() {
        return List.of();
    }
}
resilient {
  retry { itemApi { delay = "100ms", attempts = 3 } }
  circuitbreaker { itemApi { slidingWindowSize = 20, minimumRequiredCalls = 10, failureRateThreshold = 50 } }
  timeout { itemApi { duration = "5s" } }
}

The annotation does not take maxAttempts, failureThreshold, or a @Backoff. There is no inline tuning - use the named resilient.* config block. See the kora-aop-resilient skill for the full configuration reference.

Transport selection

ModuleArtifactHTTP/2HTTP/3Note
OkHttpClientModulehttp-client-okyesyesDefault choice
AsyncHttpClientModulehttp-client-asyncnonoAsync HTTP Client based
JdkHttpClientModulehttp-client-jdkyesnoJDK built-in client

Only one transport module is plugged into @KoraApp at a time. See okhttp-transport-reference.

Imperative client

Inject the base HttpClient and build requests by hand when a declarative interface does not fit:

HttpClientRequest request = HttpClientRequest.of("POST", "http://localhost:8090/pets/{petId}")
        .templateParam("petId", "1")
        .queryParam("page", 1)
        .header("token", "12345")
        .body(HttpBody.plaintext("refresh"))
        .build();
CompletionStage<HttpClientResponse> response = httpClient.execute(request);

Common pitfalls

SymptomFix
@HttpClient(baseUrl = ...) does not compile@HttpClient has no baseUrl. Use configPath and supply url in config.
"Required dependency not found" for the clientPlug a transport module (OkHttpClientModule) into @KoraApp and add annotation-processors.
@Json body not serializedAdd json-module and JsonModule; annotate the DTO with @Json.
Interceptor header change ignoredBuild a new request with request.toBuilder().header(...).build(); do not mutate in place.
Wrong @InterceptWith importIt is ru.tinkoff.kora.http.common.annotation.InterceptWith.
Non-2xx silently swallowedNon-2xx throws HttpClientResponseException unless you return HttpResponseEntity<T> or use @ResponseCodeMapper.
@Retry(maxAttempts = ...) rejectedResilient annotations take a config name string; tune values under resilient.*.

References

Source of truth: .kora-agent/kora-docs/mkdocs/docs/en/documentation/http-client.md; examples .kora-agent/kora-examples/guides/java/kora-java-guide-http-client-app and kora-java-guide-http-client-advanced-app.

Related skills

  • kora-http-server - inbound @HttpController
  • kora-openapi-generator-client - generate clients from an OpenAPI spec
  • kora-aop-resilient - @Retry, @CircuitBreaker, @Timeout, @Fallback
  • kora-json - @Json DTOs

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.