Generate a Client

The client generator targets Spring’s RestClient.

Use it when you want typed client methods generated from your tapik contracts while keeping ownership of actual HTTP client configuration in application code.

Use dev.akif.tapik:spring-restclient to enable it.

The spring-restclient module carries the shared Spring HTTP mapping helpers from dev.akif.tapik:common-spring transitively, so typical application builds only need the spring-restclient dependency.

Enable the generator

Add the module dependency and enable the generator block:

dependencies {
    implementation("dev.akif.tapik:spring-restclient:<version>")
}

tapik {
    springRestClient { }
}

You can rename the generated client surface if the default suffix does not fit your project:

tapik {
    springRestClient {
        clientSuffix("Api")
    }
}

What gets generated

The generator contributes Kotlin source files under:

  • build/generated/sources/tapik/main/kotlin/<package>/…​

For one source file of endpoint declarations, tapik generates:

  • an aggregate client interface such as CatalogApiClient,

  • one nested client contract per endpoint,

  • one generated response model per endpoint,

  • one per-endpoint uri(…​) helper that builds a concrete java.net.URI from the endpoint reference.

interface CatalogApiClient : CatalogApiEndpoints.GetProduct.Client

interface CatalogApiEndpoints {
    interface GetProduct {
        sealed interface Response : TapikResponse {
            data class Ok(
                val body: ProductView
            ) : Response

            data class NotFound(
                val body: ProblemDetails
            ) : Response
        }

        companion object {
            fun uri(productId: String): java.net.URI =
                dev.akif.tapik.renderURI(
                    CatalogApi.getProduct.path,
                    CatalogApi.getProduct.parameters.item1 to CatalogApi.getProduct.parameters.item1.codec.encode(productId)
                )
        }

        interface Client : dev.akif.tapik.spring.restclient.RestClientBasedClient {
            fun getProduct(
                productId: String,
                xRequestId: java.util.UUID
            ): Response
        }
    }
}

The important part is not the exact spelling. The important part is the shape:

  • method parameters come from the endpoint contract,

  • input headers declared with concrete HeaderValues(…​) stay baked into the endpoint and are sent automatically,

  • optional query parameters keep their defaults,

  • each endpoint contract can render its concrete request URI through uri(…​),

  • multi-output endpoints become sealed response hierarchies.

How execution works

Generated client methods delegate to RestClientInterpreter.

That interpreter is responsible for:

  • sending the request with the declared method, the generated endpoint uri(…​), headers, and optional body,

  • validating the returned status against the endpoint outputs,

  • validating response content type when the output body declares one.

After that, the generated method decodes headers and bodies and constructs the endpoint-specific response type.

Use the client in application code

Generated clients are regular interfaces. You provide a configured RestClient and inherit the generated methods:

@org.springframework.stereotype.Component
class HttpCatalogClient(
    override val restClient: org.springframework.web.client.RestClient
) : CatalogApiClient

This keeps the split clear:

  • tapik owns the generated contract and protocol mapping,

  • your application owns base URLs, auth, timeouts, interceptors, and bean wiring.

Boundaries

The generator does not:

  • create Spring beans for you,

  • pick base URLs,

  • hide every HTTP concern behind a higher-level domain API.

That is deliberate. tapik generates the typed transport contract. Your application decides how to compose it into services.