Servers and Clients with Aleph and http-kit

How to choose between Aleph and http-kit for Clojure HTTP work, with current dependency examples and realistic async server and client patterns.

Aleph and http-kit are both credible choices for Clojure HTTP work, but they feel different in practice. Aleph tends to fit systems that already embrace streams, backpressure, and asynchronous composition around Manifold and Netty. http-kit often fits teams that want a smaller surface area and a straightforward way to run an HTTP server or asynchronous client without carrying a heavier abstraction model.

The key decision is not “which library is faster in theory?” The better question is “which concurrency and operational model fits the service we are actually building?”

Use Current Dependency Setup

For a modern project, show dependencies in deps.edn. Keep the structure current, and pin the Aleph and http-kit coordinates to the latest releases from their project docs when you create a real project:

1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}
3        aleph/aleph {:mvn/version "REPLACE_WITH_CURRENT"}
4        http-kit/http-kit {:mvn/version "REPLACE_WITH_CURRENT"}}}

If the surrounding codebase still uses another build tool, consume the same libraries there. The important thing is that the example no longer assumes project.clj as the only default.

Aleph Fits Stream-Oriented Systems

Aleph is built around Netty and works especially well when you need:

  • non-blocking request handling
  • streaming request or response bodies
  • WebSocket or TCP work alongside HTTP
  • deeper integration with Manifold’s deferreds and streams

A minimal HTTP server still looks compact:

 1(ns myapp.aleph-server
 2  (:require [aleph.http :as http]))
 3
 4(defn handler [_request]
 5  {:status 200
 6   :headers {"content-type" "application/json"}
 7   :body "{\"status\":\"ok\"}"})
 8
 9(defn start! []
10  (http/start-server handler {:port 8080}))

That is easy enough. The real benefit appears when the response is naturally asynchronous or streaming.

http-kit Fits Smaller HTTP-Focused Services

http-kit is often attractive when the team wants:

  • a lightweight HTTP server
  • asynchronous client calls without adopting a larger streaming model
  • simple WebSocket support
  • a lower cognitive load for everyday service work
 1(ns myapp.httpkit-server
 2  (:require [org.httpkit.server :as http]))
 3
 4(defn handler [_request]
 5  {:status 200
 6   :headers {"content-type" "text/plain; charset=utf-8"}
 7   :body "ok"})
 8
 9(defn start! []
10  (http/run-server handler {:port 8080}))

This is a good fit when the service’s main complexity lives in domain logic and persistence rather than in streaming or connection choreography.

Clients Need Timeout and Failure Policy, Not Just Syntax

Client examples should not stop at “here is how to send a GET.” The important production concerns are:

  • connection timeout
  • request timeout
  • retry policy
  • circuit breaking or bulkheading outside the client call
  • structured logging and metrics

A simple http-kit asynchronous client call might look like this:

 1(ns myapp.httpkit-client
 2  (:require [org.httpkit.client :as client]))
 3
 4(defn fetch-json [url on-complete]
 5  (client/get url
 6              {:timeout 3000
 7               :headers {"accept" "application/json"}}
 8              (fn [{:keys [status body error]}]
 9                (on-complete
10                 (if error
11                   {:transport-error true
12                    :message (.getMessage error)}
13                   {:status status
14                    :body body})))))

The value here is not the callback itself. It is that the response shape is normalized so the rest of the program can reason about failure without knowing the raw library contract.

Aleph Becomes More Valuable When the Response Is Deferred

Aleph’s style becomes clearer when the service pipeline is already asynchronous:

1(ns myapp.aleph-client
2  (:require [aleph.http :as http]
3            [manifold.deferred :as d]))
4
5(defn fetch-body [url]
6  (-> (http/get url {:request-timeout 3000})
7      (d/chain (fn [response]
8                 {:status (:status response)
9                  :body (slurp (:body response))}))))

This kind of composition is attractive when your service already models downstream work as deferred values rather than immediate blocking steps.

Choose by Operational Shape

Prefer Aleph when:

  • the service already uses Manifold
  • streaming is a real requirement
  • the team is comfortable with asynchronous composition
  • connection lifecycle and flow control matter

Prefer http-kit when:

  • the service mostly needs ordinary HTTP handling
  • simplicity matters more than an advanced async model
  • you want a smaller API surface for day-to-day work
  • the team does not need richer stream semantics

The wrong choice is usually the one whose concurrency model the team does not actually want to own.

A Better Comparison Model

    flowchart LR
	    A["Clojure Service"] --> B{"Need stream-heavy or deferred-first design?"}
	    B -- Yes --> C["Aleph + Manifold"]
	    B -- No --> D["http-kit"]
	    C --> E["HTTP, WebSocket, Streaming, Non-blocking Pipelines"]
	    D --> F["HTTP, Simpler Async Client/Server, Lower Surface Area"]

This is a better mental model than treating both libraries as interchangeable “fast web server” badges.

Key Takeaways

  • Use current dependency examples instead of project.clj-only setup.
  • Choose Aleph when streaming and deferred-first composition are part of the actual service design.
  • Choose http-kit when you want a simpler HTTP server/client model with less conceptual overhead.
  • Normalize client failure and timeout behavior instead of scattering raw library responses across the codebase.
  • Evaluate libraries by operational fit and concurrency model, not by generic performance marketing.

References and Further Reading

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026