Open-source project
dakrone/clj-http avatar
dakrone/clj-http

clj-http: a Ring-shaped client over the Apache HttpComponents library

An idiomatic clojure http client wrapping the apache client. Officially supported version.

1,827 stars410 forksClojureMIT

At a glance

What is it?
clj-http sends requests as Ring maps and layers functionality on top with middleware, so every option in the request map has a documented transformation. The branch layout is the other thing to understand before you depend on it.
Who is it for?
Adopt clj-http on the 3.x branch if you want an HTTP client that shares a data format with your Ring server, since request options and response metadata arrive as maps you can thread and merge. Do not adopt the master branch for production: the documentation says it is 4.x and unreleased, and it is based on version 5 of the Apache HTTP Client.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 100 days ago.
What is it written in?
Mainly Clojure, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • dakrone/clj-http1,827 ★ · +1
Star history
Repository2026-10-162026-11-07
dakrone/clj-http1.8k1.8k

1 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

One data format for the request and the response

clj-http wraps the Apache HttpComponents client, and the design is borrowed wholesale from Ring, the protocol Clojure HTTP servers use. A request is a Ring request map, the response comes back as a Ring response map, and the function that does the work is `clj-http.client/request`.

The consequence is that an HTTP call is data, not a side effect. You can build a request map, pass it through a function, log it, retry it, and hand it to `request`. That is the property the library is actually selling, and it is why the option names in the documentation are the interesting part rather than the method names.

Convenience methods sit on top as sugar: `get`, `head`, `put`, `post`, `delete`, `copy`, `move`, `patch`, and `options`. Nothing about them is privileged. `(client/get url opts)` is `request` with the method already set, and if the sugar does not fit you are one call away from the real thing.

The library took over from mmcgrana's clj-http, which matters only for provenance: the name is inherited, the codebase is not.

Options are a map, and the README shows both header styles

The quickstart is built around showing options rather than explaining them, and the GET examples are dense with what the request map accepts.

clojure
(require '[clj-http.client :as client])
clojure
(client/get "http://example.com/resources/id")

;; Setting options
(client/get "http://example.com/resources/3" {:accept :json})
(client/get "http://example.com/resources/3" {:accept :json :query-params {"q" "foo, bar"}})

;; Specifying headers as either a string or collection:
(client/get "http://example.com"
            {:headers {"foo" ["bar" "baz"], "eggplant" "quux"}})

;; Using either string or keyword header names:
(client/get "http://example.com"
            {:headers {:foo ["bar" "baz"], :eggplant "quux"}})

Two details in those lines repay attention. Header names may be strings or keywords interchangeably, and a header value may be a collection, which is how you send a repeated header. `:accept :json` is a convenience the coercion middleware understands, and `:query-params` takes a map rather than a pre-built query string, so you are not concatenating strings by hand.

Cookies are also an option, and the default is not to have them: `(client/post "http://example.com" {:cookie-policy :none})` disables them entirely, and the documentation covers cookie stores, keystores, and trust stores as separate topics. `:cookie-policy` is the kind of key that tells you the library is exposing Apache's configuration rather than inventing its own abstraction.

The same map shape applies to `head`, with or without an options argument, and to every other verb.

Three branches, and only one of them is for you

The branch layout is stated up front and it decides your dependency coordinates.

The 2.x branch is no longer maintained except for security issues. The 3.x branch holds the current stable releases and is also the main GitHub branch. The master branch is 4.x, unreleased, and based on version 5 of the Apache HTTP Client.

That third line is the one to note. The repository's default branch is 3.x, which is the stable branch. If you clone and build from master expecting to match Clojars, you will not.

The version matrix ties into it. The installation section gives `[clj-http "3.13.1"]` for Leiningen or Boot, with `[clj-http "2.3.0"]` available if you need an older release. Then the Clojure requirements differ by minor: 3.12.x supports Clojure 1.6.0 and higher, 3.13.x supports 1.8.0 and higher, and 4.x is stated to support 1.7.0 and higher.

So there are two separate axes to check before pinning, the HTTP client generation and the Clojure baseline, and they do not move together.

Coercions are middleware, not configuration

The design puts input and output coercion in the same middleware stack as everything else, which is the Ring philosophy applied consistently. The documentation's table of contents lists Coercions split into Input coercion and Output coercion, alongside Headers, Query-string parameters, Meta Tag Headers, Link Headers, Redirects, Cookies, Exceptions, Decompression, and Debugging.

That list is a map of the problems that come up in practice rather than a feature list. Link Headers and Meta Tag Headers mean you can traverse pagination and read machine-readable metadata without writing a header parser. Decompression means gzip and deflate responses are handled as an option rather than something you negotiate per call. Exceptions is a section, so there is a defined error type rather than an unchecked Java exception escaping from the client.

Redirects gets its own subsection on creating a custom RedirectStrategy, which is the point where the Apache layer becomes visible. That is the escape hatch pattern throughout the library: there is an idiomatic option for the common case, and there is a documented way to reach the underlying Java object when you need it.

Authentication follows the same structure, with separate sections for Basic Auth, Digest Auth, NTLM Auth, and oAuth2, and caching is a middleware section of its own rather than an option flag.

Async requests, cancellation, and reuse

The advanced sections are where the library stops being a convenience wrapper and becomes something you can build on.

Async HTTP Request is a documented part of the quickstart rather than an appendix, and it comes with a section on cancelling requests. That pairing is the important part: a client that can issue a request out of band and cannot cancel it is a resource leak waiting for a traffic spike. Cancellation existing means you can implement a timeout or a circuit breaker in your own code instead of relying on one global setting.

Persistence is addressed in two sections, Persistent Connections and Re-using HttpClient between requests. The second is the one that changes performance characteristics. A client created per request throws away connection pooling, and the difference is visible on any service making many small calls. Reusing one `HttpClient` across requests is a supported pattern, not an accident of implementation.

There is also a section on modifying Apache-specific features of the HttpClientBuilder and HttpAsyncClientBuilder, which is the deepest level of access offered. If you need to change something the Clojure API does not expose, that is where you do it, and knowing that section exists saves an hour of reading the Java sources.

Raw requests, middlewares, and the other escape hatches

A Raw Request section covers the case where you want no wrapping at all, and it documents Boolean options alongside it. Those two belong together: when you bypass the middleware stack you also give up the defaults that stack applies, including options that default to true, so the boolean flags are how you turn behaviour back on.

Custom Middleware is documented, which means the transformation chain is extensible rather than closed. That is the practical consequence of the Ring design, and it is also the reason the request map can grow options: each piece of middleware reads keys from it, adds keys to it, and passes it on.

The remaining advanced topics are Incremental JSON Parsing, which matters when a response is too large to hold as one string, and DNS Resolution, which matters when you need control over which resolver is used.

Proxies is a section as well, so routing through a proxy is a documented option rather than an environment variable you have to discover.

One note on packaging: the README documents a clj-http-lite variant under Optional Dependencies. If your dependency graph is a problem in a Clojure application, that is the lever to look at before deciding to write the calls yourself.

Examples, troubleshooting entries, and the licence

The repository carries an `examples/` directory with five runnable files, and the names map onto the features above: `body_coercion.clj`, `caching_middleware.clj`, `kubernetes_pod.clj`, `logging-apache-requests.clj`, and `progress_download.clj`.

Two of those are worth opening before writing any code. `logging-apache-requests.clj` is the answer to the Debugging and Logging sections, since it shows how to see what Apache itself did rather than only what the wrapper did. `progress_download.clj` is the answer to Incremental JSON Parsing and streaming.

The troubleshooting section names two specific failures, which tells you what people actually hit. One is a `VerifyError` about `org.codehaus.jackson.smile.SmileParser` overriding the final method `getBinaryValue`, which is a classpath conflict with the Jackson Smile parser rather than a clj-http bug. The other is a `NoHttpResponseException` caused by stale connections, which is a connection pooling problem between a client and a server that closes idle connections without warning.

There is also a section on Other Libraries Providing Middleware, and a section on faking responses for tests, which matters because the library's test story depends on being able to stub an HTTP call.

The licence is MIT. The repository is not archived and the last push was on 2026-07-30, and it publishes no GitHub releases, so Clojars is where you find versioned artifacts. The default branch for the repository is 3.x, which is the stable line, though the branch documentation also describes master as the unreleased 4.x work.

Editorial conclusion

Adopt clj-http on the 3.x branch if you want an HTTP client that shares a data format with your Ring server, since request options and response metadata arrive as maps you can thread and merge. Do not adopt the master branch for production: the documentation says it is 4.x and unreleased, and it is based on version 5 of the Apache HTTP Client. Verify first which Clojure version you are on, because 3.12.x and 3.13.x have different minimums.

Frequently asked questions

How do I add clj-http to a Leiningen or Boot project?

Add the dependency and require the client namespace. The documented coordinates are `[clj-http "3.13.1"]` in project.clj, with `[clj-http "2.3.0"]` available if you need the older line, then `(require '[clj-http.client :as client])`.

Which branch should I depend on for clj-http?

The 3.x branch holds the current stable releases and is the main GitHub branch. The 2.x branch is maintained only for security issues, and master is 4.x, unreleased, based on version 5 of the Apache HTTP Client. Mind the Clojure floor too: 3.12.x supports 1.6.0 and higher while 3.13.x requires 1.8.0 or higher.

Why do I get a NoHttpResponseException from clj-http?

The troubleshooting section lists this as caused by stale connections, which happens when a server closes idle connections without warning and the client tries to reuse one. Persistent connections and reusing a single HttpClient between requests are documented in the advanced usage section.

Official sources

  1. dakrone/clj-http on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dakrone-clj-http.svg)](https://hysenlabs.com/projects/dakrone-clj-http)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/dakrone-clj-http.svg)](https://hysenlabs.com/projects/dakrone-clj-http)

Was this useful?