Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Retrofit Admin & Architecture Guide

Generated June 2026 from square/retrofit at 8e83a24

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for engineers who need to understand how Retrofit works beneath the annotation surface: the runtime request pipeline, the converter/call-adapter SPI, the multi-module repository layout, the build and release system, and the library's security and operational characteristics. Where the User Guide explains how to use Retrofit, this guide explains how Retrofit is built — enough to extend it, integrate it deeply, debug release-only failures, or contribute to it.

Covers Retrofit at commit 8e83a24 (release line 3.0.0, development version 3.1.0-SNAPSHOT).


1. Executive Summary

Retrofit is a type-safe HTTP client for Android and the JVM, maintained by Square under the Apache 2.0 license. Its central idea: you declare an API as an annotated Java/Kotlin interface, and Retrofit generates a working implementation at runtime using a java.lang.reflect.Proxy. All actual networking is delegated to OkHttp; Retrofit's job is the adaptation layer — turning a method invocation into an OkHttp request, and an OkHttp response back into your declared type.

Attribute Value
Language Java (core), with Kotlin (KotlinExtensions.kt) and Kotlin modules
License Apache License 2.0
Group / coordinates com.squareup.retrofit2
Current release 3.0.0 (2025-05-15)
Minimum runtime Java 8+ or Android API 21+
Core runtime dependency OkHttp 4.12 (api scope → transitive; brings transitive Kotlin)
Build system Gradle (multi-module), Java toolchain pinned to JDK 8 language level
Distribution Maven Central, multi-release JARs, a BOM
Module count Core + 11 converter modules + 6 adapter modules + mock + BOM + response-type-keeper

Architectural one-liner: a dynamic proxy resolves each interface method, once, into a cached ServiceMethod that pairs a parsed request template (RequestFactory) with a response converter and a call adapter; invoking the method builds an OkHttpCall and adapts it to the declared return type.


2. System Architecture

2.1 High-level request pipeline

   Your interface (annotated)
            │  retrofit.create(MyApi.class)
            ▼
   java.lang.reflect.Proxy ── InvocationHandler
            │  method invoked
            ▼
   Retrofit.loadServiceMethod(service, method)   ◄── ConcurrentHashMap cache (per Method)
            │  (parse once, reuse forever)
            ▼
   ServiceMethod.parseAnnotations
       ├── RequestFactory.parseAnnotations  ── parses @GET/@Path/@Query/... into a request template
       └── HttpServiceMethod.parseAnnotations
              ├── resolves a CallAdapter  (from callAdapterFactories)
              └── resolves a response Converter<ResponseBody, T>  (from converterFactories)
            │
            ▼
   HttpServiceMethod.invoke(args)
       └── new OkHttpCall<>(requestFactory, args, callFactory, responseConverter)
            │
            ▼
   CallAdapter.adapt(call)  ──►  Call<T> | Observable | CompletableFuture | suspend result
            │
            ▼
   okhttp3.Call.Factory (OkHttpClient)  ── real HTTP I/O

2.2 The proxy and per-method caching — Retrofit.create

Retrofit.create(Class<T>) returns a Proxy.newProxyInstance whose InvocationHandler:

  1. Defers java.lang.Object methods (equals, hashCode, toString) to normal invocation.
  2. For Kotlin/Java default (interface) methods, invokes them directly via the platform's Reflection strategy.
  3. For everything else, calls loadServiceMethod(service, method).invoke(proxy, args).

loadServiceMethod is the concurrency-critical core. It maintains a ConcurrentHashMap<Method, Object> whose values progress monotonically through three states:

  1. absent — no thread has begun parsing this method.
  2. a lock object — one thread is parsing; others block on that lock.
  3. a ServiceMethod — parsing complete; the happy path returns it immediately.

The first thread to putIfAbsent a fresh lock wins, parses annotations, stores the result, and releases. On a parsing exception it removes the lock so other waiters retry (and observe the real exception, rather than a cryptic ClassCastException — a deliberate fix from 2.11.0). This makes interface parsing thread-safe and pay-once.

Note

Annotation parsing uses reflection and is comparatively expensive — which is exactly why each ServiceMethod is built once and cached. Build your Retrofit instance and its proxies once and reuse them.

2.3 Parsing annotations — ServiceMethod → RequestFactory + HttpServiceMethod

ServiceMethod.parseAnnotations does two things:

  • Builds a RequestFactory from the method's HTTP annotations — the reusable template that knows how to turn args into an okhttp3.Request (path substitution, query/header/field/part handlers, body converter selection).
  • Validates the return type (rejects void and unresolvable type variables/wildcards), then delegates to HttpServiceMethod.parseAnnotations.

HttpServiceMethod.parseAnnotations resolves the two pluggable pieces and chooses a concrete subclass:

Subclass When Behavior
CallAdapted Normal (non-suspend) methods callAdapter.adapt(call)
SuspendForResponse Kotlin suspend fun … : Response<T> awaitResponse(call, continuation)
SuspendForBody Kotlin suspend fun … : T await / awaitNullable / awaitUnit

For suspend functions, Retrofit synthesizes an adapter type of Call<ResponseType> (unwrapping Response<T> when present) and forces the SkipCallbackExecutor annotation so the coroutine isn't double-dispatched through the callback executor. It also rejects suspend fun returning Call<T> (always incorrect) and enforces that @HEAD uses Void/Unit.

Important

SuspendForBody deliberately catches all Throwable subtypes (except fatal VirtualMachineError/ThreadDeath/LinkageError) and routes them through KotlinExtensions.suspendAndThrow. This avoids Java's UndeclaredThrowableException when a checked exception is thrown synchronously across the proxy boundary — a subtle but important correctness property of coroutine support.

2.4 Executing the request — OkHttpCall

HttpServiceMethod.invoke constructs an OkHttpCall<> that wraps the requestFactory, the invocation args, the okhttp3.Call.Factory, and the response converter, then hands it to the call adapter. OkHttpCall lazily creates the underlying okhttp3.Call, executes (execute()) or enqueues (enqueue()) it, applies the response converter to a successful body, and surfaces error bodies via Response.errorBody(). A single OkHttpCall is single-use; clone() produces a fresh one.

2.5 Platform abstraction — Platform

Platform is a static-initializer singleton that selects runtime strategies by inspecting System.getProperty("java.vm.name"):

VM Callback executor Reflection strategy Built-in factories
Dalvik (Android), SDK ≥ 24 AndroidMainExecutor (main thread) Reflection.Android24 BuiltInFactories.Java8
Dalvik, SDK < 24 AndroidMainExecutor Reflection BuiltInFactories
RoboVM none (synchronous) Reflection BuiltInFactories
default JVM none (synchronous) Reflection.Java8 BuiltInFactories.Java8

This is why callbacks land on the Android main thread but on the calling background thread on the plain JVM, and why default-interface-method invocation differs by Java version (handled through a multi-release JAR — see §5.2).

2.6 Factory ordering — Retrofit.Builder.build

Order matters because Retrofit tries factories in list order until one returns non-null.

Converter factories are assembled as:

[ BuiltInConverters ]  +  [ your converters, in add order ]  +  [ platform default converters ]

BuiltInConverters is added first and cannot be overridden — this both prevents you from breaking core behavior (raw ResponseBody/RequestBody/Void/Unit handling) and ensures correct behavior when a user converter (Moshi, Gson) would otherwise greedily consume all types. The platform defaults at the tail include the Optional converters.

Call adapter factories are assembled as:

[ your adapters, in add order ]  +  [ platform default adapters ]

The defaults include DefaultCallAdapterFactory (plain Call<T>) and, on Java 8+, CompletableFutureCallAdapterFactory. The callback Executor (Android main thread, or null on the JVM) is threaded into the default call adapters at build time.

Tip

The resolution loop exposes a clean diagnostic: when no factory matches, Retrofit throws an exception that lists the factories it Skipped and Tried. Read that list to see exactly which converters/adapters were registered and in what order.


3. The Extension SPI

Retrofit's extensibility rests on two abstract factory classes. Every converter and adapter module in the repository is a thin implementation of one of them.

3.1 Converter.Factory

public interface Converter<F, T> {
  @Nullable T convert(F value) throws IOException;

  abstract class Factory {
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) { return null; }

    public @Nullable Converter<?, RequestBody> requestBodyConverter(
        Type type, Annotation[] parameterAnnotations,
        Annotation[] methodAnnotations, Retrofit retrofit) { return null; }

    public @Nullable Converter<?, String> stringConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) { return null; }
  }
}

A factory overrides whichever of the three it supports and returns null for types it doesn't handle:

  • responseBodyConverter — ResponseBody → your type (the Call<T> body).
  • requestBodyConverter — your type → RequestBody (for @Body, @Part, @PartMap).
  • stringConverter — your type → String (for @Field, @FieldMap, @Header, @HeaderMap, @Path, @Query, @QueryMap). When none matches, Retrofit falls back to BuiltInConverters.ToStringConverter (Object.toString()).

Helpers getParameterUpperBound(int, ParameterizedType) and getRawType(Type) (delegating to Utils) make generic-type inspection ergonomic.

3.2 CallAdapter.Factory

public interface CallAdapter<R, T> {
  Type responseType();      // body type used to prepare the Call (e.g. Repo for Call<Repo>)
  T adapt(Call<R> call);    // wrap the Call into the target type

  abstract class Factory {
    public abstract @Nullable CallAdapter<?, ?> get(
        Type returnType, Annotation[] annotations, Retrofit retrofit);
  }
}

get(...) inspects the method's declared return type and returns an adapter, or null to defer to the next factory. responseType() tells Retrofit which body type to deserialize into — note it is typically not the same as the method's return type (for Single<User> the response type is User).

3.3 Worked example: a delegating converter

The Optional converters (Guava/Java 8) are the canonical "delegating" pattern — they don't parse bytes; they call retrofit.nextResponseBodyConverter(...) for the inner type and wrap a possibly-null result in an Optional. This is why an Optional converter must be registered before the converter that does the real parsing: it needs to delegate to it.


4. Repository Structure

The repository is a Gradle multi-module build. The core library is small; most modules are thin SPI implementations plus their test suites.

retrofit/                          ← repository root
├── retrofit/                      ← CORE library (retrofit2.*)
│   ├── src/main/java/retrofit2/   ← Retrofit, ServiceMethod, OkHttpCall, etc.
│   │   ├── http/                  ← the 25 annotations (@GET, @Path, ...)
│   │   └── internal/              ← EverythingIsNonNull (internal-only)
│   ├── src/main/java14/           ← multi-release: Java 14 default-method reflection
│   ├── src/main/java16/           ← multi-release: Java 16 default-method reflection
│   ├── android-test/              ← Android instrumentation tests
│   ├── java-test/  kotlin-test/   ← JVM + Kotlin unit tests
│   ├── robovm-test/               ← RoboVM (iOS) platform test
│   └── test-helpers/
├── retrofit-converters/           ← gson, jackson, moshi, protobuf, wire,
│   │                                simplexml, jaxb, jaxb3, kotlinx-serialization,
│   │                                scalars, guava, java8
├── retrofit-adapters/             ← rxjava, rxjava2, rxjava3, guava, java8, scala
├── retrofit-mock/                 ← MockRetrofit, BehaviorDelegate, NetworkBehavior
├── retrofit-bom/                  ← bill-of-materials (java-platform)
├── retrofit-response-type-keeper/ ← annotation processor for shrinker keep-rules
├── samples/                       ← runnable examples (SimpleService, Crawler, ...)
├── website/                       ← Astro Starlight docs site (src/content/docs/*)
└── build.gradle / settings.gradle

Note

The authoritative prose documentation lives in website/src/content/docs/ (index.md, declarations.md, configuration.md, download.mdx, contributing.md). The website/public/ tree contains generated Dokka/Javadoc HTML for published versions and is build output, not source.

4.1 Core type inventory (retrofit2)

Type Role
Retrofit / Retrofit.Builder Configuration entry point + proxy factory
ServiceMethod (abstract) Per-method parsed model; parseAnnotations
RequestFactory Parsed HTTP-annotation template → okhttp3.Request
HttpServiceMethod (+ CallAdapted, SuspendForResponse, SuspendForBody) Binds request template to converter + adapter; bridges coroutines
OkHttpCall Call<T> backed by an okhttp3.Call
Call / Callback / Response Public request/response API
Converter / CallAdapter Extension SPI
BuiltInConverters / BuiltInFactories Default conversions and factory sets
DefaultCallAdapterFactory / CompletableFutureCallAdapterFactory Default adapters
OptionalConverterFactory Public delegating converter for Optional
Platform / Reflection / AndroidMainExecutor Runtime-platform strategy
ParameterHandler / RequestBuilder Per-parameter request assembly
Invocation Captures service Class, method, and arguments for interceptors
HttpException Thrown for non-2xx responses on body-returning suspend calls
KotlinExtensions.kt await/awaitResponse/suspendAndThrow coroutine bridge
Utils Type utilities (raw type, bounds, validation)

5. Build System

5.1 Gradle configuration

The root build.gradle applies cross-cutting configuration to all subprojects:

  • Java toolchain language level 8 (JavaLanguageVersion.of(8)) — the library targets Java 8 bytecode for maximum compatibility.
  • Error Prone with several checks escalated to ERROR: MissingFail, MissingOverride, UnusedException, UnusedMethod, UnusedNestedClass, UnusedVariable, WildcardImport.
  • Spotless formatting: googleJavaFormat for Java, ktfmt (Google style) for Kotlin.
  • Animal Sniffer signature checks: every java-library is verified against the java18 signature and (except the Java 8-specific converter/adapter modules) the Android API level 21 signature — this is how the library guarantees its "Java 8+ / Android API 21+" floor.
  • A copyWebsiteDocs task assembles Dokka/Javadoc output into website/public/3.x for publishing.

5.2 Multi-release JAR

The core retrofit module compiles three source sets — main (Java 8), java14, and java16 — and packages the version-specific classes under META-INF/versions/{14,16} via a multi-release JAR. These hold the reflection needed to invoke interface default methods on newer JDKs. The JAR manifest sets Automatic-Module-Name: retrofit2 and Multi-Release: true. Each module sets its own automatic module name (e.g. retrofit2.converter.gson, retrofit2.adapter.rxjava3).

Caution

Static-analysis tools unaware of multi-release JARs may flag the Java 14/16 bytecode classes. This is expected and harmless; the correct class is selected by the runtime JDK.

5.3 Dependency scopes

The core module's build.gradle declares OkHttp as api (so it leaks transitively to consumers — intentional, since you interact with OkHttp types) and Android + kotlinx-coroutines as compileOnly (optional at runtime; present only when you use Android callbacks or coroutines). Each converter/adapter module declares api projects.retrofit plus its target library (api libs.gson, api libs.rxjava3, etc.), keeping the dependency graph explicit per module.

5.4 Testing matrix

The java-test module is configured to run the unit suite across every JDK from 8 through 24 (a testJdkN task per version, using Azul Zul toolchains), wiring each into check. There are dedicated Kotlin (kotlin-test), Android instrumentation (android-test), and RoboVM (robovm-test) suites so coroutine behavior, the Android main-thread executor, and the iOS/RoboVM platform path are all exercised.


6. Operations & Integration

Retrofit is a library, not a running service — "operations" here means integrating and operating it correctly inside an application.

// One client, one Retrofit, reused process-wide.
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(Duration.ofSeconds(10))
    .addInterceptor(authInterceptor)
    .addInterceptor(loggingInterceptor)
    .build();

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")   // MUST end in /
    .client(client)
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
    .validateEagerly(isDebugBuild)         // surface bad declarations at startup
    .build();
  • Singletons. A Retrofit instance is immutable and thread-safe; share it. The connection pool and dispatcher live on the OkHttpClient.
  • validateEagerly(true) parses and validates every method in the interface at create(...) time. Enable in debug/CI to catch malformed declarations at startup rather than on first call; leave off in production to avoid the upfront reflection cost.
  • Timeouts, retries, caching, auth all belong on the OkHttpClient, not Retrofit.

6.2 Threading model

Return type Where work runs Where the result is delivered
Call.execute() Calling thread (blocks) Calling thread
Call.enqueue() OkHttp dispatcher thread Android: main thread · JVM: the dispatcher thread
RxJava Observable/Single/… Per RxJava subscribeOn/observeOn As scheduled
CompletableFuture OkHttp dispatcher thread Completing thread
suspend fun OkHttp dispatcher thread Resumes on the coroutine's dispatcher

The Android main-thread callback behavior comes from AndroidMainExecutor selected in Platform; on the JVM there is no callback executor, so callbacks run on the background request thread.

6.3 Error handling model

  • For Call, Callback.onResponse fires for every HTTP response. Check Response.isSuccessful() (status in [200,300)); parse errorBody() for failures. onFailure fires only for I/O or request/response processing errors.
  • For body-returning suspend functions, a non-2xx response throws HttpException (carrying code(), message(), and the full response()), while network failures throw IOException.

6.4 Performance characteristics

  • First call per method pays reflection cost (annotation parsing); subsequent calls hit the ServiceMethod cache.
  • Streaming. @Streaming avoids buffering large response bodies into memory; the caller must consume and close the ResponseBody.
  • Deferred serialization. Since 2.12.0, first-party converters (Gson, Jackson, Moshi, Protobuf, Wire) offer a withStreaming() factory that defers request-body serialization to HTTP-write time — moving conversion off the calling thread when using Call.enqueue.

7. Security Analysis

Retrofit is a client library with a deliberately small security surface; most concerns live in how you configure it and OkHttp.

Area Posture / guidance
Transport security TLS, certificate pinning, and trust configuration are OkHttp's responsibility. Configure OkHttpClient (e.g. CertificatePinner) and pass it to .client(...). Always use https:// base URLs.
Credentials Inject Authorization/API-key headers via an OkHttp Interceptor, not per-method @Header (avoids leaking secrets into many call sites and into logs).
URL encoding @Path/@Query/@Field values are URL-encoded by default. Setting encoded = true opts out — only do so for values you have already correctly encoded, or you risk request-splitting/injection.
Header injection allowUnsafeNonAsciiValues = true permits non-ASCII header values that are not strictly HTTP-compliant; use only when a server requires it, and never with untrusted input.
Untrusted input → URL @Url lets the whole URL come from a runtime value. Validate/whitelist host and scheme when the value originates from user or server data, since a full URL on the endpoint can replace the base host entirely.
Deserialization Body parsing is delegated to the chosen converter (Gson/Jackson/etc.). Deserialization-safety (polymorphic type handling, resource limits) is governed by that library's configuration, not Retrofit.
Shrinker correctness R8 rules ship automatically; ProGuard requires manual rules (retrofit2.pro) or generic type info can be stripped, causing release-only failures (see §8).
Dependency provenance Publications are signed (signAllPublications=true) and pushed to Maven Central; verify checksums/signatures in supply-chain-sensitive builds.

Vulnerability reporting follows the repository's BUG-BOUNTY.md policy.


8. Troubleshooting (Operational)

IllegalArgumentException: Could not locate ResponseBody converter for <type> / … RequestBody converter …

The factory list had no match. The exception lists Skipped and Tried factories — confirm the converter is registered and ordered correctly (delegating/Optional converters before greedy ones like Moshi/Gson). Remember BuiltInConverters is always first and immovable.

IllegalArgumentException: Could not locate call adapter for <type>

No CallAdapter.Factory handled the return type. Register the matching adapter (addCallAdapterFactory(...)). The default factories only handle Call<T> and (Java 8+) CompletableFuture<T>.

Release build fails with Method return type must not include a type variable or wildcard or Unable to create converter

Shrinking stripped generic signatures. Ensure R8 (rules automatic), or add retrofit2.pro for ProGuard plus OkHttp/Okio rules. For interfaces whose runtime type needs exceed static rules, use the retrofit-response-type-keeper annotation processor.

ClassCastException from a second caller after a parsing failure

Fixed in 2.11.0 — the parsing exception is now visible to all concurrent callers, not just the first. If you observe this, you are on a pre-2.11 version; upgrade.

UndeclaredThrowableException from a suspend function

Should not occur on 3.x — SuspendForBody/SuspendForResponse intercept synchronous throwables and route them through the continuation. If seen, verify you are not wrapping the suspend call in a custom proxy that bypasses this handling.

JAXB converter ignores @XmlJavaTypeAdapter

Open issue #3349 in JaxbConverterFactory. Track upstream; work around by relocating the adapter to a binding level the JAXB implementation honors.


9. Extending Retrofit

9.1 Writing a converter factory

public final class YamlConverterFactory extends Converter.Factory {
  public static YamlConverterFactory create() { return new YamlConverterFactory(); }

  @Override public Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] annotations, Retrofit retrofit) {
    return body -> yaml.loadAs(body.charStream(), (Class<?>) type);
  }

  @Override public Converter<?, RequestBody> requestBodyConverter(
      Type type, Annotation[] p, Annotation[] m, Retrofit retrofit) {
    return value -> RequestBody.create(yaml.dump(value), MediaType.get("application/yaml"));
  }
}

Return null from any method for types you don't handle so Retrofit can try the next factory. Install with addConverterFactory(YamlConverterFactory.create()).

9.2 Writing a call adapter factory

Extend CallAdapter.Factory, inspect returnType in get(...), and return a CallAdapter whose responseType() is the body type and whose adapt(Call<R>) wraps the call into your target type (delegating to call.execute()/call.enqueue(...)). The built-in RxJava/Guava/Java 8 adapters are the reference implementations; the samples/ module includes ErrorHandlingAdapter and DynamicBaseUrl as worked extension examples.

9.3 Reading request metadata with Invocation

Invocation (attached to the OkHttp request as a tag) exposes the service Class<?>, the invoked Method, and the arguments. An OkHttp interceptor can read it for logging, metrics, or routing — samples/InvocationMetrics.java demonstrates per-endpoint timing. The newer Invocation.annotationUrl returns the original URL declared on the method annotation.


10. Contributing

Contribution flow (from website/src/content/docs/contributing.md):

  1. Fork the repository and open a pull request from a branch.
  2. Follow existing conventions and style — formatting is enforced by Spotless (googleJavaFormat / ktfmt Google style).
  3. Ensure the build passes: ./gradlew build (gradlew.bat build on Windows). This runs Error Prone, Animal Sniffer signature checks, Spotless, and the multi-JDK test matrix.
  4. Sign the Individual Contributor License Agreement before code can be merged.

Release & distribution

  • gradle.properties pins GROUP=com.squareup.retrofit2; the development version is 3.1.0-SNAPSHOT.
  • Publishing uses com.vanniktech.maven.publish with mavenCentralPublishing=true, mavenCentralAutomaticPublishing=true, and signAllPublications=true.
  • In-development snapshots are published to the Central Portal Snapshots repository.
  • The retrofit-bom module publishes a constraints-only platform so consumers can align all module versions.

11. Version & Compatibility Notes

Aspect Detail
Current release 3.0.0 (2025-05-15) — upgraded to Kotlin-based OkHttp 4.12 (from 3.14)
Binary compatibility 3.x is forward binary-compatible with 2.x; 2.x-compiled libraries run on 3.x
Transitive Kotlin Introduced in 3.0 via OkHttp 4.12
Recent additions (Unreleased) Explicit keep rules for RxJava Result types; allowoptimization flags; Invocation.annotationUrl
2.12.0 Streaming serialization (withStreaming()) for Gson/Jackson/Moshi/Protobuf/Wire; primitive @Tag support
2.11.0 OptionalConverterFactory made public; concurrent parsing-exception visibility fix
2.10.0 kotlinx.serialization + JAXB 3 converters; BOM; Unit response type; non-ASCII header values; response-type-keeper processor

12. Quick Reference

Need API
Build a client new Retrofit.Builder().baseUrl(url).build()
Add JSON support .addConverterFactory(GsonConverterFactory.create())
Add a return-type adapter .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
Custom HTTP client .client(okHttpClient)
Validate at startup .validateEagerly(true)
Derive a variant config retrofit.newBuilder()…build()
Generate an implementation retrofit.create(MyApi.class)
Resolve a converter manually retrofit.responseBodyConverter(type, annotations)
Resolve an adapter manually retrofit.callAdapter(returnType, annotations)
Inspect registered factories retrofit.converterFactories() / retrofit.callAdapterFactories()
Single-use call → fresh copy call.clone()
Non-2xx as exception (coroutines) catch HttpException