2026-04-22·6 min read·sota.io team

Deploy Clojure to Europe — EU Hosting for Ring, Reitit & http-kit in 2026

Clojure occupies a quiet but influential corner of the European technology landscape. It is not the language you hear about at every conference — but it is the language running some of the most sophisticated financial and data-intensive systems on the continent. The Finnish company Metosin built Reitit, Malli, and Virhe — the standard-setting Clojure web libraries that the global community depends on — from Helsinki. Nubank, the world's largest neobank by market cap, runs its entire platform on the JVM with Clojure at the core, and it is expanding into European markets. Deutsche Bank and several DACH financial institutions run Clojure for derivatives pricing and risk systems where the language's persistent data structures and Software Transactional Memory make concurrent financial modeling safer than almost any other approach.

For EU developers building on the JVM who want functional programming without leaving the JVM ecosystem, Clojure is the pragmatic choice. This guide covers deploying a Clojure web application — using Ring, Reitit, and http-kit — to European infrastructure with sota.io, the EU-native PaaS running on German servers.

Why Clojure for EU Financial and Data Applications

Clojure's appeal in EU financial services and data engineering comes from a specific set of properties that map directly onto the problems those teams face:

Persistent immutable data structures. Clojure's core data structures — vectors, maps, sets, lists — are immutable and structurally shared. Modifying a map does not copy the map; it creates a new version that shares unchanged structure with the old one. For audit trails, financial ledgers, and event-sourced systems where immutability is both a best practice and a regulatory requirement, Clojure makes the right approach the default approach.

Software Transactional Memory. Clojure's ref system provides coordinated, atomic updates across multiple state values. This is essential for financial systems where account balances, position sizes, and order states must update atomically — or not at all. STM eliminates entire categories of race conditions that Java developers manage manually with locks.

Spec and Malli for data validation. Clojure Spec and the more modern Malli library (from Finnish company Metosin) provide runtime data validation with automatic schema inference, generative testing, and human-readable error messages. For GDPR compliance, API validation, and financial data integrity, Malli's data-driven schema approach integrates with Ring middleware and Reitit's coercion system to validate all incoming data at the boundary.

REPL-driven development. Clojure's interactive development model — connecting your editor to a running JVM instance and evaluating forms in production — enables a debugging and exploration workflow that compiled languages cannot match. For data scientists and quant analysts building financial models, the ability to inspect live state, modify functions, and re-evaluate without restarting is a significant productivity advantage.

Full JVM ecosystem access. Clojure runs on the JVM. Every Java library works from Clojure via Java interop. If your organization uses Apache Kafka, Apache Flink, ElasticSearch, JDBC drivers, or any JVM library, Clojure works with all of it — often with better ergonomics than Java, because Clojure's data-first approach avoids the boilerplate of Java's object model.

Ring, Reitit, and http-kit: The Clojure Web Stack

Clojure's web ecosystem is built around composable abstractions rather than opinionated frameworks:

Ring is the HTTP abstraction at the center of Clojure's web stack. A Ring handler is a function from a request map to a response map. Middleware is a function from handler to handler. The entire model is data: HTTP requests and responses are Clojure maps, and the composition is pure function composition. There is no annotation-based magic, no dependency injection container, no reflection.

(defn hello-handler [request]
  {:status 200
   :headers {"Content-Type" "application/json"}
   :body "{\"message\": \"Hello from EU\"}"})

;; Middleware wraps handlers
(defn wrap-logging [handler]
  (fn [request]
    (println "Request:" (:uri request))
    (handler request)))

(def app (-> hello-handler
             wrap-logging
             wrap-json-response))

Reitit (from Metosin, Helsinki) is the modern Clojure routing library. It is data-driven: routes are Clojure data structures, not macros or annotations. This enables runtime introspection, automatic OpenAPI generation, and schema-based coercion using Malli.

(require '[reitit.ring :as ring]
         '[reitit.coercion.malli :as rcm]
         '[reitit.ring.coercion :as rrc]
         '[malli.core :as m])

(def router
  (ring/router
   [["/api/v1"
     ["/users"
      {:get  {:summary "List users"
              :responses {200 {:body [:vector {:id :uuid :email :string}]}}
              :handler (fn [_] {:status 200 :body (list-users)})}
       :post {:summary "Create user"
              :parameters {:body {:email :string :name :string}}
              :responses {201 {:body {:id :uuid}}}
              :handler (fn [{{{:keys [email name]} :body} :parameters}]
                         {:status 201 :body (create-user! email name)})}}]]
    ["/health"
     {:get {:handler (fn [_] {:status 200 :body {:status "ok"}})}}]]
   {:data {:coercion rcm/coercion
           :middleware [rrc/coerce-exceptions-middleware
                        rrc/coerce-request-middleware
                        rrc/coerce-response-middleware]}}))

http-kit is the async HTTP server. It handles concurrent connections efficiently with non-blocking I/O and is simple to embed in an uberjar:

(require '[org.httpkit.server :as server])

(def server (server/run-server
             (ring/ring-handler router)
             {:port (Integer/parseInt (System/getenv "PORT"))}))

next.jdbc and HugSQL handle database access. next.jdbc is the modern JDBC wrapper — ergonomic, composable, and fully compatible with PostgreSQL, which sota.io provisions automatically.

Project Structure

A minimal production Clojure API:

my-api/
├── deps.edn                 # tools.deps dependency file
├── build.clj                # uberjar build script
├── Dockerfile
└── src/
    └── my_api/
        ├── core.clj         # entry point + server startup
        ├── routes.clj       # Reitit router definition
        ├── handlers.clj     # Ring handler functions
        ├── db.clj           # next.jdbc database setup
        └── middleware.clj   # custom Ring middleware

deps.edn manages dependencies:

{:paths ["src" "resources"]
 :deps  {org.clojure/clojure        {:mvn/version "1.12.0"}
         metosin/reitit              {:mvn/version "0.7.2"}
         metosin/reitit-ring         {:mvn/version "0.7.2"}
         metosin/reitit-malli        {:mvn/version "0.7.2"}
         http-kit/http-kit           {:mvn/version "2.8.0"}
         com.github.seancorfield/next.jdbc {:mvn/version "1.3.939"}
         org.postgresql/postgresql   {:mvn/version "42.7.3"}
         ring/ring-json              {:mvn/version "0.5.1"}
         ch.qos.logback/logback-classic {:mvn/version "1.5.6"}}
 :aliases
 {:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"}}
          :ns-default build}}}

Dockerfile for sota.io

sota.io detects Dockerfiles automatically. Build the uberjar in the image:

FROM clojure:temurin-21-tools-deps AS builder
WORKDIR /app
COPY deps.edn build.clj ./
# Cache dependencies
RUN clojure -P
COPY src/ src/
COPY resources/ resources/
RUN clojure -T:build uber

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/my-api.jar .
ENV PORT=8080
EXPOSE 8080
CMD ["java", "-jar", "my-api.jar"]

build.clj creates the uberjar:

(ns build
  (:require [clojure.tools.build.api :as b]))

(def class-dir "target/classes")
(def uber-file "target/my-api.jar")

(defn uber [_]
  (b/copy-dir {:src-dirs ["src" "resources"] :target-dir class-dir})
  (b/compile-clj {:basis (b/create-basis {:project "deps.edn"})
                  :src-dirs ["src"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis (b/create-basis {:project "deps.edn"})
           :main 'my-api.core}))

Deploy to sota.io

# Install the sota CLI
curl -fsSL https://sota.io/install.sh | sh

# Authenticate
sota auth set-key <your-api-key>

# Deploy from your project directory
cd my-api && sota deploy

sota.io automatically:

Access DATABASE_URL in your Clojure app:

(require '[next.jdbc :as jdbc])

(def datasource
  (jdbc/get-datasource {:jdbcUrl (System/getenv "DATABASE_URL")}))

Performance on the JVM

MetricJVM (Temurin 21)GraalVM Native
Cold start3–8s50–120ms
RAM150–300 MB25–60 MB
ThroughputHighHigh
CompilationFastSlow build
EcosystemFull JVMLimited reflection

For most production Clojure APIs, JVM Temurin 21 with tiered compilation is the right choice. GraalVM native compilation works for simpler services but requires careful handling of reflection and dynamic classloading, which Clojure's macros and dynamic dispatch use extensively.

The JVM's JIT compilation means throughput is excellent under sustained load. Cold starts are longer than Go or Rust, but for server-side APIs that stay warm (which is the normal production state), the throughput difference is negligible.

GDPR Compliance on sota.io

sota.io runs exclusively on Hetzner infrastructure in Germany. Your Clojure application's data — database contents, logs, uploaded files — never leaves the EU by default. No GDPR exceptions to manage, no Data Transfer Agreements to negotiate with US cloud providers.

For Clojure teams building applications subject to GDPR Article 17 (right to erasure), Article 20 (data portability), or Article 25 (privacy by design), the EU data residency is not a configuration choice — it is the infrastructure default.

The Metosin Ecosystem

A notable aspect of Clojure's web stack is its European origin. Metosin, a Finnish software consultancy, has produced some of the most influential Clojure libraries in production worldwide:

This is unusual: a European company producing foundational infrastructure for a language ecosystem used globally. For EU-based Clojure teams, there is something fitting about deploying on EU infrastructure a language stack whose core libraries were designed in Helsinki.

Getting Started

Deploy a Clojure Ring/Reitit API to European infrastructure in three steps:

  1. Build your uberjar with the multi-stage Dockerfile above
  2. Run sota deploy from your project directory
  3. Set any additional environment variables via the sota.io dashboard

Your Clojure API is live on German infrastructure, PostgreSQL 17 provisioned, TLS handled. The first deployment takes 3–6 minutes (JVM layer caching). Subsequent deploys are faster.

sota.io supports Clojure 1.10+, any version of the Temurin JDK, GraalVM native if your build produces a binary, and all standard Clojure tooling (Leiningen and tools.deps both work, since both output a standard JAR).

Deploy your Clojure app to Europe →