An idiomatic Clojure wrapper over the official Anthropic Java SDK
(com.anthropic/anthropic-java).
Build a request as a Clojure map, get a Clojure map back.
Every other Clojure Anthropic library hand-rolls HTTP against the REST API, which means each one is perpetually chasing Anthropic's surface and tends to fall behind. This one wraps Anthropic's own actively-maintained Java SDK instead, so streaming, tool use, retries, and every new model and feature arrive the moment Anthropic ships them in Java - you just get a Clojure-shaped API on top: maps in, maps out, keywords for roles and block types.
Leiningen (project.clj):
[net.clojars.savya/anthropic-sdk-clj "0.1.0"]
tools.deps (deps.edn):
net.clojars.savya/anthropic-sdk-clj {:mvn/version "0.1.0"}
Set ANTHROPIC_API_KEY in your environment (or pass :api-key to client).
(require '[anthropic.core :as anthropic])
(def client (anthropic/client)) ; reads ANTHROPIC_API_KEY
;; A single message. :model defaults to "claude-opus-4-8", :max-tokens to 1024.
(anthropic/create-message
client
{:model "claude-opus-4-8"
:max-tokens 1024
:system "You are concise."
:messages [{:role :user :content "Name three primary colors."}]})
;; => {:id "msg_..." :model "claude-opus-4-8" :role :assistant
;; :stop-reason :end-turn
;; :content [{:type :text :text "Red, blue, yellow."}]
;; :usage {:input-tokens 18 :output-tokens 9}}
stream-text calls your callback with each text delta and returns the full text.
(anthropic/stream-text
client
{:model "claude-opus-4-8" :max-tokens 256
:messages [{:role :user :content "Write a haiku about parentheses."}]}
#(print %)) ; prints each delta as it arrives
;; => returns the complete string when the stream ends
Declare tools as maps; tool_use blocks come back parsed, and you complete the
loop by echoing the assistant turn and sending a :tool-result block.
(def weather-tool
{:name "get_weather"
:description "Get the current weather for a city"
:input-schema {:type "object"
:properties {:city {:type "string"}}
:required ["city"]}})
(def ask {:role :user :content "What's the weather in Paris?"})
(def r1 (anthropic/create-message
client {:tools [weather-tool] :messages [ask]}))
;; r1 :stop-reason is :tool-use; find the call:
(def call (first (filter #(= :tool-use (:type %)) (:content r1))))
;; => {:type :tool-use :id "toolu_..." :name "get_weather" :input {:city "Paris"}}
;; Run the tool yourself, then send the result back:
(anthropic/create-message
client
{:tools [weather-tool]
:messages [ask
{:role :assistant :content (:content r1)}
{:role :user :content [{:type :tool-result
:tool-use-id (:id call)
:content "18°C and sunny"}]}]})
;; => {... :content [{:type :text :text "It's 18°C and sunny in Paris."}]}
create-message - request map ↔ response mapstream-text - incremental text deltastool_use blocks, tool_result round-tripoutput_config.format), batches, filesUnit tests (the request/response translation) run with no network:
lein test
The :integration suite hits the live API and is billed - it needs
ANTHROPIC_API_KEY and is run explicitly:
ANTHROPIC_API_KEY=sk-... lein test :integration
Copyright © 2026 Savyasachi
Distributed under the Eclipse Public License 2.0.
The wrapped com.anthropic/anthropic-java SDK is MIT-licensed and remains the
property of Anthropic.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |