Working Kotlin code to call the Claude API in 2026. No official SDK — use OkHttp or Ktor. Covers streaming SSE, suspend functions, Android integration, and Spring Boot backend.
Anthropic has no official Kotlin SDK as of 2026, but calling the Claude REST API from Kotlin is straightforward using OkHttp, Ktor, or the built-in Java HttpClient. All examples use idiomatic Kotlin: data classes, coroutines, and string templates.
// build.gradle.kts
// implementation("com.squareup.okhttp3:okhttp:4.12.0")
// implementation("org.json:json:20240303")
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
val client = OkHttpClient()
val apiKey = System.getenv("ANTHROPIC_API_KEY")
fun askClaude(prompt: String): String {
val body = JSONObject().apply {
put("model", "claude-sonnet-4-6")
put("max_tokens", 1024)
put("messages", listOf(mapOf("role" to "user", "content" to prompt)))
}.toString()
val request = Request.Builder()
.url("https://api.anthropic.com/v1/messages")
.addHeader("x-api-key", apiKey)
.addHeader("anthropic-version", "2023-06-01")
.addHeader("content-type", "application/json")
.post(body.toRequestBody("application/json".toMediaType()))
.build()
client.newCall(request).execute().use { response ->
val json = JSONObject(response.body!!.string())
return json.getJSONArray("content").getJSONObject(0).getString("text")
}
}
fun main() {
println(askClaude("Explain Kotlin coroutines in one paragraph."))
}
// implementation("io.ktor:ktor-client-core:2.3.12")
// implementation("io.ktor:ktor-client-cio:2.3.12")
// implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
// implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
@Serializable
data class ClaudeRequest(
val model: String,
val max_tokens: Int,
val messages: List<Map<String, String>>
)
@Serializable
data class ClaudeResponse(
val content: List<Map<String, String>>
)
val httpClient = HttpClient(CIO) {
install(ContentNegotiation) { json() }
}
suspend fun askClaudeAsync(prompt: String): String {
val response: ClaudeResponse = httpClient.post("https://api.anthropic.com/v1/messages") {
header("x-api-key", System.getenv("ANTHROPIC_API_KEY"))
header("anthropic-version", "2023-06-01")
contentType(ContentType.Application.Json)
setBody(ClaudeRequest(
model = "claude-sonnet-4-6",
max_tokens = 1024,
messages = listOf(mapOf("role" to "user", "content" to prompt))
))
}.body()
return response.content.first()["text"] ?: ""
}
fun main() = runBlocking {
println(askClaudeAsync("What is the capital of France?"))
}
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
fun streamClaude(prompt: String) {
val body = JSONObject().apply {
put("model", "claude-sonnet-4-6")
put("max_tokens", 1024)
put("stream", true)
put("messages", listOf(mapOf("role" to "user", "content" to prompt)))
}.toString()
val request = Request.Builder()
.url("https://api.anthropic.com/v1/messages")
.addHeader("x-api-key", System.getenv("ANTHROPIC_API_KEY"))
.addHeader("anthropic-version", "2023-06-01")
.addHeader("content-type", "application/json")
.post(body.toRequestBody("application/json".toMediaType()))
.build()
OkHttpClient().newCall(request).execute().use { response ->
response.body?.byteStream()?.bufferedReader()?.lines()?.forEach { line ->
if (line.startsWith("data: ") && line != "data: [DONE]") {
val data = JSONObject(line.removePrefix("data: "))
if (data.getString("type") == "content_block_delta") {
val delta = data.getJSONObject("delta")
if (delta.getString("type") == "text_delta") {
print(delta.getString("text"))
System.out.flush()
}
}
}
}
}
println()
}
fun main() = streamClaude("Write a haiku about Kotlin.")
// Proxies Claude requests from a mobile app — API key stays server-side
@RestController
@RequestMapping("/api")
class ClaudeController {
private val okHttp = OkHttpClient()
@PostMapping("/chat")
fun chat(@RequestBody req: Map<String, String>): ResponseEntity<String> {
val prompt = req["message"] ?: return ResponseEntity.badRequest().build()
val reply = askClaude(prompt) // reuse helper from minimal example above
return ResponseEntity.ok(reply)
}
}
| Library | Coroutines | Android | Extra dep? | Best for |
|---|---|---|---|---|
| OkHttp | Callbacks / enqueue | Yes | Yes (~700 KB) | Android backends, existing OkHttp users |
| Ktor HttpClient | Native suspend | Via Android engine | Yes (~2 MB full) | Idiomatic Kotlin, multi-platform |
| Java HttpClient | CompletableFuture | No (API 26+) | None (JDK 11+) | Zero-dep JVM services |
Estimate the token cost of your Kotlin-Claude integration with the Claude API Cost Calculator. For the Java equivalent, see the Java example. For MCP server patterns, see the Python MCP server guide.