Enkan is a minimal Java web framework inspired by Ring (Clojure) and
Connect (Node.js).
It is built around one conviction: a web application should be explicit, traceable, and operable — not magical.
Most mainstream Java frameworks (Spring Boot, Quarkus, Jakarta EE) excel at hiding complexity.
Enkan takes the opposite approach: it makes every piece of request processing visible, composable, and inspectable.
| Concern | Typical framework | Enkan |
|---|---|---|
| Configuration | application.yml, classpath scanning | Plain Java code — no files |
| Middleware ordering | Implicit (annotation-driven, auto-configured) | Explicit app.use(...) calls |
| Capabilities on request | Always present (e.g. getSession() always available) | Added by middleware — absent until you use(new SessionMiddleware()) |
| Startup surprises | Framework injects beans you didn’t expect | Only what you wired |
Because there are no configuration files, your IDE can rename, refactor, and statically analyse the entire application setup.
When a request arrives, you can read the middleware stack from top to bottom in a single Java file and follow every transformation.
There is no AOP weaving, no annotation processor magic, and no “auto-configuration” that silently adds behaviour.
The REPL lets you inspect the live stack at any time:
enkan> /middleware app list
ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware@4929dbc3)
ANY trace (enkan.middleware.TraceMiddleware@1c985ffd)
ANY session (enkan.middleware.SessionMiddleware@32424a32)
ANY routing (kotowari.middleware.RoutingMiddleware@226c7147)
Annotations are useful, but overuse turns code into configuration disguised as code.
Enkan uses annotations only where compile-time metadata is genuinely needed (e.g. @Middleware to declare middleware dependencies).
Controllers, components, and middleware are plain Java classes.
This is the most technically distinctive aspect of Enkan.
The Middleware interface carries four type parameters:
interface Middleware<REQ, RES, NREQ, NRES> {
<NNREQ, NNRES> RES handle(REQ req, MiddlewareChain<NREQ, NRES, NNREQ, NNRES> chain);
}
REQ/RES are the types this middleware accepts; NREQ/NRES are the types it passes to the next middleware.
A middleware that adds session support can change the type of the request object, and the compiler verifies that downstream middleware actually expects the enriched type.
In frameworks that use raw HttpServletRequest, the compiler cannot catch a middleware being placed in the wrong order.
Enkan catches such errors at compile time.
When SessionMiddleware runs, it dynamically adds the WebSessionAvailable interface to the request object via a JDK proxy:
request = MixinUtils.mixin(request, WebSessionAvailable.class);
Until that middleware has run, getSession() does not exist on the request.
This makes which capabilities are available a function of which middleware you have registered — not a fixed API.
The result is that:
getSession() before SessionMiddleware has run (it would be a compile error).Enkan cleanly separates two concerns that many frameworks conflate:
| Concern | Enkan concept |
|---|---|
| Stateful services (DB pool, template engine, HTTP server) | SystemComponent managed by EnkanSystem |
| Per-request processing logic | Middleware in the application stack |
Dependencies between components are declared explicitly:
EnkanSystem.of(
"datasource", new HikariCPComponent(...),
"doma", new DomaProvider(),
"template", new FreemarkerComponent(),
"app", new ApplicationComponent("com.example.MyAppFactory"),
"http", new JettyComponent()
).relationships(
component("http").using("app"),
component("app").using("template", "doma", "datasource"),
component("doma").using("datasource")
);
The system starts components in dependency order and stops them in reverse.
There is no classpath scanning to discover components — the graph is the documentation.
Changing a controller or middleware class triggers a sub-second application reset (not a JVM restart).
The component system restarts only the application layer, keeping long-initialisation resources (DB pools, etc.) alive.
enkan> /reset # ~1 second
@Middleware(dependencies = {"cookies"}) on SessionMiddleware means the system will
reject an application stack that places SessionMiddleware before CookiesMiddleware at startup,
with a clear message — not a NullPointerException at request time.
TraceMiddleware records timestamps at each layer. The accumulated trace is written to the
X-Enkan-Trace response header, giving you a per-request flamegraph without external tooling.
Enkan’s REPL is not just for server control — it is a powerful tool for understanding and validating business logic interactively.
Because Enkan enforces clean architecture, domain objects have no dependencies on framework layers (no @Entity, no @Component, no servlet API imports). This means you can instantiate and manipulate them directly in the REPL:
enkan> var order = new Order("customer-123", List.of(
new OrderItem("SKU-001", 3),
new OrderItem("SKU-042", 1)));
enkan> order.totalAmount()
$2 ==> 4800
enkan> order.applyDiscount(DiscountPolicy.LOYALTY)
enkan> order.totalAmount()
$4 ==> 4320
This brings Clojure-style REPL-driven development to Java:
enkan> var ds = system.getComponent("datasource")
enkan> var dao = new CustomerDao(ds)
enkan> var customer = dao.findById(42L)
enkan> customer.creditLimit()
$3 ==> 500000
enkan> var eligibility = LoanEligibility.evaluate(customer)
enkan> eligibility.reason()
$5 ==> "Approved: credit score above threshold"
In Spring Boot, domain objects often carry JPA annotations or depend on injected services, making standalone instantiation impractical. In Enkan, the separation is structural — domain objects are always plain Java, always testable, and always REPL-friendly.
JVM startup is fast because there is no classpath scanning.
A typical Enkan application starts in under 3 seconds, including database migrations.
enkan> /start # start the system
enkan> /stop # stop gracefully
enkan> /reset # hot-reload application
enkan> /routes app # inspect routing table
enkan> /middleware app predicate serviceUnavailable ANY # toggle maintenance mode live
enkan> /connect 64815 # attach to a running process via JShell
Runtime predicate changes mean you can enable maintenance mode, toggle feature flags,
or inspect internal state without redeployment.
| Enkan | Spring Boot | Quarkus | Vert.x | |
|---|---|---|---|---|
| Configuration | Java code only | YAML + annotations | properties + annotations | Java + JSON |
| Classpath scanning | None | Extensive | Build-time | None |
| Middleware type safety | Compile-time (generics) | Runtime (Filter chain) | Runtime | Runtime |
| Component lifecycle | Explicit graph | @Bean + @Autowired | CDI / @ApplicationScoped | Verticle |
| REPL / live inspection | Built-in (JShell, domain objects) | Actuator (HTTP only) | Dev UI (browser only) | – |
| Reactive / async | Opt-in | WebFlux opt-in | Reactive opt-in | Core feature |
| Target scale | Small–medium apps | All scales | Cloud-native | High-concurrency |
Enkan is not trying to replace Spring Boot for large enterprise systems.
It is a deliberate choice for teams that value clarity and control over
convention and automation.
Kotowari is a Rails-like web framework built on top of Enkan.
It adds:
r.resource(CustomerController.class))Kotowari inherits Enkan’s philosophy: it is explicit, thin, and composed entirely of middleware.