Skip to content
Back to skills

Clojure Hato

ASecurity

Modern HTTP client for Clojure wrapping JDK 11+ java.net.http. Use when working with HTTP requests, REST APIs, async HTTP calls, WebSockets, or needing HTTP/2 support.

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
documentationgojavagitapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill clojure-hato --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Clojure Hato?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Clojure Hato
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-clojure-hato/badge)](https://www.skillsdirectory.com/skills/majiayu000-clojure-hato)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: clojure-hato
description: Modern HTTP client for Clojure wrapping JDK 11+ java.net.http. Use when working with HTTP requests, REST APIs, async HTTP calls, WebSockets, or needing HTTP/2 support.
---

# hato

Modern HTTP client for Clojure wrapping JDK 11's HttpClient with support for HTTP/1.1, HTTP/2, sync/async requests, and WebSockets.

Requires JDK 11 or above.

## Setup

deps.edn:
```clojure
hato/hato {:mvn/version "1.0.0"}
```

Leiningen:
```clojure
[hato/hato "1.0.0"]
```

Require:
```clojure
(require '[hato.client :as hc])
```

See https://clojars.org/hato/hato for the latest version.

## Quick Start

```clojure
;; Simple GET request
(hc/get "https://httpbin.org/get")
; => {:status 200, :body "{...}", :headers {...}, :request-time 112, ...}

;; POST with JSON
(hc/post "https://httpbin.org/post"
  {:form-params {:a 1 :b 2}
   :content-type :json})

;; GET with query params
(hc/get "https://httpbin.org/get"
  {:query-params {:q "search term" :page 1}})

;; Async request (returns CompletableFuture)
@(hc/get "https://httpbin.org/get" {:async? true})
```

## Core Concepts

Built-in client vs reusable client:
- Without `:http-client` option: creates single-use client per request
- With reusable client: connection pooling, persistent connections, better performance

Creating a reusable client:
```clojure
(def client (hc/build-http-client
              {:connect-timeout 10000
               :redirect-policy :normal}))

(hc/get "https://example.com" {:http-client client})
```

Response coercion with `:as`:
- `:string` (default) - returns body as string
- `:json` / `:json-string-keys` - parses JSON (requires cheshire)
- `:byte-array` - returns raw bytes
- `:stream` - returns java.io.InputStream
- `:auto` - auto-detects based on content-type

## Common Patterns

### Building a Reusable Client

```clojure
(def http-client
  (hc/build-http-client
    {:connect-timeout 10000        ; connection timeout (ms)
     :redirect-policy :normal       ; :never :normal :always
     :cookie-policy :all            ; :none :all :original-server
     :version :http-2}))            ; :http-1.1 :http-2

;; Use for all requests
(hc/get url {:http-client http-client})
```

### Sync vs Async Requests

```clojure
;; Synchronous - blocks until response
(hc/get "https://example.com")

;; Async - returns CompletableFuture
(let [future (hc/get "https://example.com" {:async? true})]
  @future)  ; deref to block for result

;; Async with callbacks
(hc/get "https://example.com"
  {:async? true}
  (fn [resp] (println "Success:" (:status resp)))  ; respond callback
  (fn [error] (println "Error:" error)))           ; raise callback
```

### Request with Headers and Auth

```clojure
;; Custom headers
(hc/get "https://api.example.com"
  {:headers {"x-api-key" "secret"
             "accept" "application/json"}})

;; Basic auth (preemptive)
(hc/get "https://api.example.com"
  {:basic-auth {:user "username" :pass "password"}})

;; OAuth bearer token
(hc/get "https://api.example.com"
  {:oauth-token "your-token-here"})
```

### Query Params and Form Data

```clojure
;; Query params (GET)
(hc/get "https://api.example.com/search"
  {:query-params {:q "clojure" :limit 10}})
; => GET /search?q=clojure&limit=10

;; Form-encoded POST
(hc/post "https://example.com/login"
  {:form-params {:username "user" :password "pass"}})
; => Content-Type: application/x-www-form-urlencoded

;; JSON POST
(hc/post "https://api.example.com/users"
  {:form-params {:name "Alice" :email "alice@example.com"}
   :content-type :json})
; => Content-Type: application/json
; => Body: {"name":"Alice","email":"alice@example.com"}
```

### Response Coercion

```clojure
;; Auto-parse JSON response (requires cheshire)
(hc/get "https://api.example.com/data" {:as :json})
; => {:status 200, :body {:key "value"}, ...}

;; Stream large responses
(with-open [stream (:body (hc/get url {:as :stream}))]
  (io/copy stream (io/file "output.bin")))

;; Get raw bytes
(hc/get "https://example.com/image.png" {:as :byte-array})
```

### Multipart File Upload

```clojure
(hc/post "https://example.com/upload"
  {:multipart [{:name "title" :content "My File"}
               {:name "file"
                :content (io/file "path/to/file.pdf")
                :filename "document.pdf"
                :content-type "application/pdf"}]})
```

### Error Handling

```clojure
;; By default, throws on 4xx/5xx status
(try
  (hc/get "https://example.com/notfound")
  (catch clojure.lang.ExceptionInfo e
    (let [{:keys [status body]} (ex-data e)]
      (println "Error" status body))))

;; Disable exception throwing
(let [{:keys [status body]} (hc/get url {:throw-exceptions? false})]
  (if (< status 400)
    (println "Success:" body)
    (println "Failed:" status)))
```

### WebSockets

```clojure
(require '[hato.websocket :as ws])

;; Create WebSocket connection (returns CompletableFuture)
(let [socket @(ws/websocket "ws://echo.websocket.events"
                {:on-message (fn [ws msg last?]
                               (println "Received:" msg))
                 :on-close (fn [ws status reason]
                             (println "Closed:" status reason))
                 :on-error (fn [ws error]
                             (println "Error:" error))})]
  ;; Send message
  @(ws/send! socket "Hello World!")

  ;; Close connection
  (ws/close! socket))
```

## Gotchas / Caveats

JDK 11+ Required: hato requires Java 11 or above. For older Java, use clj-http instead.

JSON/Transit Dependencies: Response coercion with `:as :json` or `:as :transit+json` requires optional dependencies:
- cheshire 5.9.0+ for JSON
- com.cognitect/transit-clj for Transit

Connection Pooling: Always create a reusable client with `build-http-client` for production use. Single-use clients (without `:http-client` option) don't pool connections.

Redirect Limit: Default max redirects is 5. Change with `-Djdk.httpclient.redirects.retrylimit=10`. Client returns 30x response with empty body when limit exceeded (no exception thrown).

Nested Query Params: Nested maps in `:query-params` are flattened by default:
```clojure
{:query-params {:a {:b {:c 5}}}}  ; => "a[b][c]=5"
```
Disable with `:ignore-nested-query-string true`.

Form Params: Nested maps in `:form-params` are NOT flattened by default. Enable with `:flatten-nested-form-params true`.

Default Timeout: Connection timeout is unlimited by default. Always set `:connect-timeout` for production:
```clojure
(hc/build-http-client {:connect-timeout 10000})  ; 10 seconds
```

Request Timeout: Per-request timeout with `:timeout` option (separate from connect-timeout):
```clojure
(hc/get url {:timeout 5000})  ; 5 second timeout for response
```

## Advanced Topics

For advanced features, see the GitHub repo and cljdoc:
- Custom middleware and request interceptors
- Client SSL/TLS certificate authentication
- Custom cookie handlers
- HTTP/2 server push
- Proxy configuration
- Custom thread executors

## References

- GitHub: https://github.com/gnarroway/hato
- API Docs: https://cljdoc.org/d/hato/hato/
- JavaDoc HttpClient: https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.