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.
Unofficial. A community library, not affiliated with or endorsed by Anthropic. It wraps Anthropic's official Java SDK; it is not itself an official Anthropic SDK.
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-clj "0.3.0"]
tools.deps (deps.edn):
net.clojars.savya/anthropic-clj {:mvn/version "0.3.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}}
create-message also accepts the optional controls :temperature, :top-p,
:top-k, :stop-sequences, :tool-choice (:auto/:any/:none or
{:type :tool :name "x"}), :thinking ({:type :enabled :budget-tokens N},
{:type :adaptive}, or {:type :disabled}), :metadata ({:user-id "..."}),
and :service-tier (:auto/:standard-only). When the response uses prompt
caching, :usage also carries :cache-creation-input-tokens and
:cache-read-input-tokens.
Pass :response-format (a JSON Schema map) to get a :parsed Clojure map back.
Object schemas must set "additionalProperties": false (an API requirement).
:effort (:low…:max) is accepted alongside or on its own.
(anthropic/create-message
client
{:max-tokens 256
:response-format {:type "object"
:properties {:capital {:type "string"}}
:required ["capital"]
:additionalProperties false}
:messages [{:role :user :content "What is the capital of France?"}]})
;; => {... :content [{:type :text :text "{\"capital\":\"Paris\"}"}]
;; :parsed {:capital "Paris"}}
count-tokens takes the same request map and returns the input-token count
without sending the message (:max-tokens and sampling params are ignored).
(anthropic/count-tokens
client
{:messages [{:role :user :content "How many tokens is this?"}]})
;; => {:input-tokens 13}
(anthropic/list-models client)
;; => [{:id "claude-opus-4-8" :display-name "Claude Opus 4.8"
;; :created-at "2026-..." :max-tokens 64000} ...]
(anthropic/get-model client "claude-opus-4-8")
;; => {:id "claude-opus-4-8" :display-name "Claude Opus 4.8" ...}
Submit many requests at the 50%-cost batch tier. Each request is
{:custom-id "..." :params <same map as create-message>}.
(def batch
(anthropic/create-batch
client
[{:custom-id "a" :params {:max-tokens 64 :messages [{:role :user :content "Hi"}]}}
{:custom-id "b" :params {:max-tokens 64 :messages [{:role :user :content "Bye"}]}}]))
;; => {:id "msgbatch_..." :processing-status :in-progress
;; :request-counts {:processing 2 :succeeded 0 ...} ...}
(anthropic/get-batch client (:id batch)) ; poll until :processing-status :ended
(anthropic/list-batches client)
(anthropic/cancel-batch client (:id batch))
;; Once ended, pull results (succeeded entries carry the parsed :message):
(anthropic/batch-results client (:id batch))
;; => [{:custom-id "a" :result {:type :succeeded :message {...}}} ...]
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
For thinking or tool-use streams, stream surfaces every normalized event. Each
event is a map keyed by :type: :message-start, :content-block-start
(:index, :block), :text-delta/:thinking-delta/:input-json-delta/
:signature-delta (:index plus payload), :content-block-stop (:index),
:message-delta (:stop-reason), and :message-stop. It still returns the full
text. To rebuild a streamed tool call, accumulate :input-json-delta
:partial-json per :index - the matching :content-block-start carries the
tool :id/:name.
(anthropic/stream
client
{:max-tokens 256 :messages [{:role :user :content "Think, then answer."}]}
(fn [ev]
(case (:type ev)
:thinking-delta (print "[thinking]" (:thinking ev))
:text-delta (print (:text ev))
nil)))
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 map, with the full set of request
controls (sampling, stop sequences, tool-choice, thinking, metadata,
service-tier), cache-token usage, and structured output (:response-format
→ :parsed, plus :effort)count-tokens - input-token count without sendingstream-text - incremental text deltasstream - every normalized stream event (message + content-block lifecycle,
text/thinking/tool-use/signature deltas)tool_use blocks, tool_result round-triplist-models / get-model - Models APIcreate-batch, get-batch, list-batches, cancel-batch,
delete-batch, batch-resultsbeta().files())Unit 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 |