User Guide

This guide walks you through installing the tapik Gradle plugin, declaring endpoints with the Kotlin DSL, and running the generators that create clients, servers, and documentation.

New to tapik? Start with Getting Started for a step-by-step tutorial before diving into the full plugin reference below.

Prerequisites

  • JDK compatible with the repository defaults (javaTargetVersion=21, toolchain javaToolchainVersion=24 in gradle.properties).

  • Gradle 9+ with the Kotlin DSL; configuration cache and task caching are supported out of the box.

  • Kotlin 2.2.20 (supplied via the Gradle toolchain).

  • Optional modules you plan to use, such as dev.akif.tapik:jackson for JSON bodies or Spring starters for generated code.

Component Example Dependency

Endpoint DSL

implementation("dev.akif.tapik:core:0.2.7")

Codecs

implementation("dev.akif.tapik:codec:0.2.7")

Jackson integration

implementation("dev.akif.tapik:jackson:0.2.7")

Gradle plugin

id("dev.akif.tapik.plugin.gradle") version "0.2.7"

The version numbers match the repository snapshot (version=0.2.7). Replace them with the release you are consuming.

Installing the Gradle Plugin

Configure the plugin in a module that produces HTTP endpoints or needs generated sources:

plugins {
    kotlin("jvm")
    id("dev.akif.tapik.plugin.gradle") version "0.2.7"
}

dependencies {
    implementation("dev.akif.tapik:core:0.2.7")
    implementation("dev.akif.tapik:jackson:0.2.7") // for JSON helpers
}

tapik {
    basePackage("com.acme.catalog")

    springRestClient { /* enables the Spring RestClient generator */ }
    springWebMvc { /* enables the Spring WebMVC generator */ }
    markdownDocumentation { /* enables the Markdown documentation generator */ }
}

Key things to remember:

  • basePackage must reference the package where your API implementations live. Only those classes (and their HttpEndpoint properties) are scanned.

  • The nested generator blocks are markers; you do not need to configure anything inside them unless future versions expose options.

  • Generated Kotlin sources land under build/generated/sources/tapik/main/kotlin and are added to the main source set automatically.

  • Non-source artefacts (for example, Markdown files) are written under build/generated.

Organizing endpoints

The scanner only looks at concrete types that implement API and at their val properties whose type is +HttpEndpoint<*, *, *>. That leads to a few practical conventions:

  • Keep the actual endpoint properties on objects or classes that implement API. Extension properties or interface properties that do not live on a concrete API implementor are not discovered.

  • To split definitions (read/write, bounded contexts) without multiplying generated clients, define helper functions in plain interfaces and use them from a single API object:

    interface ReadUsersDefs {
        fun HttpEndpointVerbBuildingContext.configureGetEndpoint() = get("users" / path.uuid("id"))
    }
    interface WriteUsersDefs {
        fun HttpEndpointVerbBuildingContext.configureCreateEndpoint() = post("users") { /* ... */ }
    }
    
    object Users : API, ReadUsersDefs, WriteUsersDefs {
        val get by endpoint { configureGetEndpoint() }
        val create by endpoint { configureCreateEndpoint() }
    }

    This keeps one generated client/controller while letting you organize the DSL.

  • Nested API objects are supported and treated as separate modules (e.g., Users.Admin produces its own generated interfaces). If you want a single generated surface, keep helpers nested but avoid making them API; place endpoints only on the parent API implementor.

  • Multiple layers of nesting work the same way: every concrete API implementor yields its own generated outputs; nesting does not merge endpoints automatically.

Declaring Endpoints

Endpoints are compile-time constants built with the DSL in the core module. The DSL collects metadata from property names, documentation strings, and nested builders.

package com.acme.catalog.endpoints

import dev.akif.tapik.*
import dev.akif.tapik.jackson.jsonBody

// Implement API to gain access to the `endpoint` DSL.
object ProductEndpoints : API {
    private val productId = path.uuid("productId")
    private val locale = query.string("locale").optional("en-US")

    val getProduct by endpoint(
        description = "Fetch product details",
        details = "Returns localized information when the locale query parameter is supplied."
    ) {
        get("products" / productId + locale)
            .input(header.uuid("X-Request-Id"))
            .output(Status.OK) {
                jsonBody<ProductView>("product")
            }
            .output(Status.NOT_FOUND) {
                jsonBody<ProblemDetails>("problem")
            }
    }
}

Implementing API is the convention to expose the endpoint DSL and keep your endpoints discoverable as properties (e.g., ProductEndpoints.getProduct).

Highlights:

  • endpoint { ... } infers the endpoint id from the property name (getProduct). The description and details flow into generated KDoc and Markdown.

  • Path building uses operator overloads: "products" / productId.

  • Query parameters append with , headers with +input { ... }, and outputs use output(Status.OK) { ... }.

  • Response variants can be chained; the DSL supports OneOf unions to model branching behaviour.

Running Generation

tapik registers an aggregated tapikGenerate task. It depends on classes for the current project and its siblings to ensure bytecode is available.

./gradlew :service:tapikGenerate

Execution flow:

  1. Compiled classes are scanned for properties that return HttpEndpoint.

  2. Metadata describing the endpoints is persisted to build/generated/tapik-endpoints.txt.

  3. The selected generators (spring-restclient, spring-webmvc, markdown-docs) run sequentially.

  4. Generated sources are attached to the IDE/classpath; you can ./gradlew build without special configuration.

You can run ./gradlew :service:classes tapikGenerate during fast feedback loops. Pass -PskipLint if you want to avoid ktlint locally.

Inspecting Outputs

  • Kotlin sources live under build/generated/sources/tapik/main/kotlin.

  • Markdown documentation is emitted to build/generated/API.md.

  • Scan report: build/generated/tapik-endpoints.txt contains the raw metadata list.

When checking generated Kotlin into source control:

  • Run ./gradlew tapikGenerate after any endpoint changes.

  • Review generated types as you would review hand-written code; they carry KDoc extracted from your endpoints.

  • For reproducibility, make sure the versions of tapik dependencies are pinned via a version catalog or BOM.

Troubleshooting

Symptom Common Cause Fix

Missing compiled classes warning

classes task did not run before tapikGenerate

Run ./gradlew classes tapikGenerate or wire tasks in your CI pipeline

No generators execute

Generator blocks omitted in the tapik extension

Add springRestClient { }, springWebMvc { }, or markdownDocumentation { }

Class not found during scan

Class outside configured basePackage

Ensure your API implementations live under the configured base package

Unexpected HTTP mappings

Endpoint path missing leading segment

Start URIs with "segment" or "segment" / ...

If you create custom generators, place their implementation on the build classpath and expose them via the Java ServiceLoader contract. The Gradle plugin will pick them up automatically when their id is enabled.