This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
./gradlew build # Build all modules — runs tests, ktlint, detekt, apiCheck, kover gate
./gradlew :sdk-core:build # Build one module
./gradlew :sdk-core:compileKotlin # Fast compile-only check
./gradlew test --tests "<FQCN>.<method>" # Run a single JUnit Platform test
./gradlew apiCheck # Binary-compatibility check against committed .api snapshots
./gradlew apiDump # Regenerate .api snapshots after INTENTIONAL public-API changes
./gradlew koverHtmlReport # Aggregate coverage report at build/reports/kover/html/All quality gates break the build: ktlint + detekt (config/detekt.yml), allWarningsAsErrors, explicit-API
strict mode, binary-compatibility-validator, and an 80% aggregate line-coverage floor (Kover, wired into the
root check task — see build.gradle.kts). Detekt is skipped on the two non-Java-8 modules,
sdk-transport-jdkhttp (11) and sdk-async-virtualthreads (21) — detekt 1.23.x crashes on the JDK-25+
system toolchain when a module targets a non-8 toolchain; see those build scripts for the upstream issue and
re-enable conditions. It runs everywhere else, including sdk-transport-okhttp.
check (so a plain ./gradlew build) also runs the R8 shrink-survival guard in the test-only
sdk-shrink-test module. That step requires a JDK 11 toolchain (Gradle auto-provisions one if absent)
and network access to Google's Maven repo to fetch com.android.tools:r8. An offline build, or one
that cannot provision JDK 11, will fail on :sdk-shrink-test:r8Run; scope the build (e.g. build specific
modules) to skip it. See that module's build.gradle.kts for the pipeline.
Eleven Gradle modules (see settings.gradle.kts). gradle/libs.versions.toml is the single source of
truth for dependency and plugin versions. Group org.dexpace, version 0.0.1-alpha.1. (Two are
unpublished and not listed below: sdk-shrink-test, a test-only R8 shrink-survival guard, and
sdk-example, a runnable end-to-end usage sample.)
| Module | Purpose | JVM target |
|---|---|---|
sdk-core |
All public contracts: HTTP models, sync + async pipelines and steps, auth, SSE, paging, pagination, serde abstractions, instrumentation, I/O seam. Zero runtime deps beyond SLF4J API (compileOnly) + Kotlin stdlib. | 8 |
sdk-io-okio3 |
Okio 3.x implementation of IoProvider — the only I/O adapter today. |
8 |
sdk-async-coroutines |
Kotlin coroutines adapter: suspend extensions, MDC propagation. |
8 |
sdk-async-reactor |
Reactor Mono/Flux adapter, incl. SSE → Flux with backpressure. |
8 |
sdk-async-netty |
Netty Future adapter with bidirectional cancellation. |
8 |
sdk-async-virtualthreads |
Virtual-thread executor adapter (AutoCloseable). |
21 |
sdk-transport-okhttp |
OkHttp 5.x HttpClient + AsyncHttpClient transport. |
8 |
sdk-transport-jdkhttp |
java.net.http.HttpClient (JEP 321) transport. |
11 |
sdk-serde-jackson |
Jackson 2.18 Serde implementation + Tristate<T> ser/de. |
8 |
Key sdk-core packages (org.dexpace.sdk.core.*): client (the HttpClient/AsyncHttpClient transport
SPIs), http.request / http.response / http.common (immutable models), http.context (context promotion
chain), http.auth (credentials, RFC 7235 challenges, Digest), http.sse (WHATWG Server-Sent Events),
http.paging (PagedIterable), http.pipeline (+.steps — stage-based sync/async pipeline runtime),
pipeline (+.step, .step.retry — recovery-aware Request/Response/Execution pipeline primitives),
pagination (Paginator + 4 strategies), serde (incl. Tristate), instrumentation (+.metrics), io,
config, util, generics. The full package map with highlights is in README.md.
docs/ (read before structural changes): architecture.md, http.md, io.md, pipelines.md,
http-body-logging-and-concurrency.md, implementation-plan.md (phased work-unit plan),
refs-comparison.md (survey of peer SDKs). styleguide/ holds the Kotlin / Kotlin-JVM style guides this
codebase follows.
The SDK is an HTTP-client toolkit, not an HTTP client. sdk-core provides abstractions, models, and
pipelines; transports plug in via HttpClient / AsyncHttpClient (two reference transports ship as
modules), and concrete I/O plugs in via IoProvider.
Layered, from the bottom up:
io/contracts —Source/Sink,BufferedSource/BufferedSink,Buffer,TeeSink. All interfaces; no concrete I/O insdk-core.Io.installProvider(provider)wires the singleIoProviderseam once at startup; a missing provider throwsIllegalStateExceptionwith the install instruction.- HTTP models — immutable
Request/Response/Headers/MediaTypeetc., private constructor +Builder+newBuilder().RequestBody.isReplayable()/toReplayable();FileRequestBodylets transports dispatchFileChannel.transferTo. - Logging bodies —
LoggableRequestBody(TeeSink mirror on write) andLoggableResponseBody(drain-once +peek()views), both withsnapshot()previews and race-safe consumed-once guards. http.context—CallContext→DispatchContext→RequestContext→ExchangeContext, each carrying anInstrumentationContext.- Pipelines — two cooperating layers, both real (no placeholders):
http.pipeline— stage-based runtime:HttpPipelineBuilder+HttpStepordered byStagewith pillar stages (exactly one REDIRECT / RETRY / AUTH / LOGGING / SERDE step per pipeline), plus the async mirror (AsyncHttpPipeline,AsyncHttpStep) and sync→async bridges.pipeline— recovery-aware primitives:RequestPipeline,ResponsePipeline,ExecutionPipeline,ResponseOutcome, with steps likeRetryStep(backoff +Retry-After),IdempotencyKeyStep,ClientIdentityStep. Seedocs/pipelines.mdbefore touching either.
- Transports —
sdk-transport-okhttp(Java 8) andsdk-transport-jdkhttp(Java 11). Both implement sync + async SPIs, propagate cancellation into the native client, and ownclose()for SDK-managed resources only (BYO clients are never closed by the SDK).
- Java 8 bytecode everywhere except
sdk-transport-jdkhttp(11) andsdk-async-virtualthreads(21). AvoidInputStream.transferTo(9+),Thread.threadId()(19+), records, sealedpermitsin Java-8 modules. A module that genuinely needs a newer JDK must override all three ofjvmToolchain(N), thejava { sourceCompatibility / targetCompatibility = VERSION_N + toolchain }block, andcompilerOptions { jvmTarget.set(JvmTarget.JVM_N) }in its own build script — overriding only the toolchain produces Java-8-format bytecode referencing newer stdlib symbols (NoSuchMethodErroron JDK 8), and omitting thejava {}block trips Gradle'scompileJava/compileKotlinJVM-target validation. Seedocs/architecture.md(Cross-Compile Toolchain Discipline). - MIT license header in every source file. Each
.kt,.java, and.ktsfile starts with the 6-lineCopyright (c) 2026 dexpace and Omar Aljarrah/SPDX-License-Identifier: MITblock — copy it from any existing file when creating new ones. Nothing enforces this automatically; it is a review convention. ReentrantLockoversynchronized(lock.withLock { … }) — synchronized pins carrier threads under Loom.- Blocking calls respect
Thread.interrupt()— catchInterruptedException, restore the interrupt flag, throwInterruptedIOException(or add the interrupt as suppressed). Seedocs/architecture.md(Cancellation). - Immutable data + private constructor +
BuilderimplementingBuilder<T>,newBuilder()pre-filled,@JvmOverloads/@JvmStaticfor Java callers. - Explicit-API strict mode on every main source set: every declaration needs an explicit visibility and
public declarations need explicit return types (
= apply { … }builder methods must declare the return type). Tests are exempt. Useinternalfor cross-package implementation details,@JvmSynthetic internalwhen the mangled name would still be Java-callable,privateotherwise. Adapter modules expose a single public entry point (e.g. onlyOkioIoProvideris public insdk-io-okio3). sdk-corehas zero non-SLF4J runtime deps — I/O, Jackson, and concurrency libraries live only in adapter modules. SLF4J iscompileOnly(added by the root build to every Kotlin module).- Published modules apply
id("dexpace.published-module")— the convention plugin in thebuild-logicincluded build (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts) carries themaven-publish+signingsetup, shared POM, staging repo, and CI-gated signing. Do not re-inline apublishing {}/signing {}block in a module; a new publishable module just applies the plugin, and a module that must not be published simply omits it. Coordinates (group/version) come fromgradle.propertiesand apply to every project. - Commit style:
feat:/test:/docs:/chore:prefixes;merge:for work-unit merge commits.
- Public API changes fail
apiCheck. Any visible signature change needs./gradlew apiDumpand the regeneratedapi/*.apifiles committed alongside the change. Never runapiDumpto silence an unintentional break. Io.installProvider(...)must run before any code touchesIo.provider. Tests installOkioIoProviderin@BeforeTest; production installs in the application startup path.- Coverage floor is aggregate 80% line coverage. The
minBound(80)rule lives on the root-aggregate:koverVerify, which the rootchecktask depends on, so a plain./gradlew buildenforces it. New under-tested code can trip the gate even when its own module builds clean — checkkoverHtmlReportto see which classes are dragging the aggregate down. - Transport tests use
mockwebserver3(OkHttp's). Follow the existing patterns insdk-transport-okhttp/sdk-transport-jdkhttptest suites rather than spinning up real sockets. allWarningsAsErrorsis on — a deprecation warning in any Kotlin module breaks the build.- There is no generated/compat code and no
sdk-core/src/main/java/tree —sdk-coreis pure Kotlin. The root Kover filter excludes only test fixtures (org.dexpace.sdk.core.testing.*); do not reintroduce a Java source tree or broad package-glob excludes.