I've been running Solon apps in production for a while now, and one thing that keeps surprising people is how the server engine works.

Not "which server does Solon use" — because the answer is: you choose.

Solon doesn't bundle a hardcoded HTTP server. The framework core is about 0.3MB. The HTTP engine is a separate plugin. You pick one. Or two. Or swap them between environments without touching a single line of application code.

Here's a quick rundown of the 10 HTTP server options, what they're good for, and how to switch.

The One-Dependency Swap

In Solon, changing your HTTP server means changing one dependency in pom.xml. Nothing else.

<!-- Default: jdkhttp (built-in JDK, 0.3MB) -->
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-web</artifactId>
</dependency>

Enter fullscreen mode Exit fullscreen mode

Want Undertow instead?

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.noear</groupId>
            <artifactId>solon-server-jdkhttp</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-server-undertow</artifactId>
</dependency>

Enter fullscreen mode Exit fullscreen mode

That's it. Your @Controller, @Mapping("/hello"), filters, interceptors — everything stays the same. The routing layer is framework-owned; the server underneath is a pluggable adapter.

The 10 Engines at a Glance

Plugin Size Protocols Reactive JDK
jdkhttp 0.3MB http 8+
smarthttp 0.8MB http, ws 8+
grizzly 1.8MB http, ws, http2 8+
vertx 6.3MB http, ws, http2 8+
jetty 2.7MB http, ws 8+
undertow 4.6MB http, ws, http2 8+
tomcat http, ws, http2 8+
jetty-jakarta 3.9MB http, ws, http2 17+
undertow-jakarta http, ws, http2 17+
tomcat-jakarta http, ws, http2 17+

Plus two dedicated WebSocket-only servers (solon-server-websocket at 0.4MB, solon-server-websocket-netty at 3.6MB) and one Socket.D server.

How to Choose

For microservices and edge services: jdkhttp or smarthttp

If you're building a lightweight API service or a microservice that needs to start in under a second, stick with jdkhttp. It's 0.3MB, requires zero external dependencies, and supports reactive I/O. The JDK built-in com.sun.net.httpserver is perfectly adequate for most API workloads.

smarthttp (0.8MB, Chinese-contributed) adds WebSocket support on the same port — useful if you need both REST and WebSocket without adding a second server.

@SolonMain
public class App {
    public static void main(String[] args) {
        Solon.start(App.class, args, app -> {
            app.enableWebSocket(true);
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

When you need HTTP/2: grizzly, undertow, vertx, or tomcat

All four support HTTP/2. Enable it with a single line:

app.onEvent(HttpServerConfigure.class, e -> {
    e.enableHttp2(true);  // v2.3.8+
});

Enter fullscreen mode Exit fullscreen mode

Undertow is a solid middle ground — 4.6MB, Apache 2.0 license, good community adoption. Vert.x is heavier (6.3MB) but gives you the full Vert.x event-loop ecosystem if you're already using it.

Traditional Java shops: Jetty or Tomcat

If your team is migrating from Spring Boot + embedded Tomcat, solon-server-tomcat is the most natural drop-in. It uses the same Tomcat v9 engine underneath — your ops team's existing tuning knowledge carries over.

Jetty is a great alternative if you prefer Eclipse ecosystem. It also has explicit JSP support via solon-server-jetty-add-jsp.

Jakarta EE migration: jetty-jakarta, undertow-jakarta, tomcat-jakarta

If you're on JDK 17+ and targeting Jakarta namespace (jakarta.*), use the Jakarta variants. They're paired with the corresponding Jakarta server versions (Jetty 12, Undertow 2.3, Tomcat 11).

The Code That Never Changes

This is the part I like most. No matter which server engine you pick, your application code looks identical:

@Controller
public class DemoController {
    @Mapping("/hello")
    public String hello() {
        return "Hello world!";
    }

    @Mapping("/greet/{name}")
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}

Enter fullscreen mode Exit fullscreen mode

The same code runs on jdkhttp (0.3MB, no dependencies), Undertow (4.6MB, HTTP/2), or Tomcat (production-hardened). Zero changes.

Advanced: Multiple Ports, SSL, and Debug Mode

All server adapters share the same configuration surface:

# app.yml
server.port: 8080
server.http.port: 8080          # alias
server.ssl.keyStore: "/data/ca/demo.jks"
server.ssl.keyPassword: "demo"

Enter fullscreen mode Exit fullscreen mode

Or programmatically:

Solon.start(App.class, args, app -> {
    // Add a second HTTP port (useful when main port is HTTPS)
    app.onEvent(HttpServerConfigure.class, e -> {
        e.addHttpPort(8082);
        e.enableDebug(true);    // debug mode for smarthttp/jdkhttp
    });
});

Enter fullscreen mode Exit fullscreen mode

Custom SSLContext (v2.5.9+):

app.onEvent(HttpServerConfigure.class, e -> {
    SSLContext sslContext = buildMyCustomSSLContext();
    e.enableSsl(true, sslContext);
});

Enter fullscreen mode Exit fullscreen mode

What About the "Default" Default?

If you add solon-web without any explicit server exclusion, Solon uses jdkhttp — the built-in JDK HTTP server. It's the lightest option (0.3MB), requires zero external JARs, and works for 90% of development scenarios.

In production, you might want something more tuned. But the beauty is: you don't decide on day one. Start with jdkhttp, benchmark, then swap to Undertow or Tomcat when you need HTTP/2 or WebSocket — all without rewriting a single route.

Honest Limitations

  • Tomcat (v9) doesn't support WebSocket directly — you need the solon-server-tomcat-add-websocket sub-plugin.
  • smarthttp had a file upload size cap (~2.1GB) before v3.0.3 — fixed now, but worth noting if you're on an older version.
  • Vert.x is the heaviest at 6.3MB — great for reactive workloads but overkill for a simple CRUD API.
  • Jakarta variants (jetty-jakarta, undertow-jakarta, tomcat-jakarta) require JDK 17+.

Summary

Solon's pluggable server architecture is one of those features that seems small on paper but makes a big difference in practice. One dependency change, zero code impact, and a full spectrum of engines from 0.3MB to full-featured production servers.

Need Pick
Lightest API server jdkhttp (0.3MB)
REST + WebSocket on same port smarthttp (0.8MB)
HTTP/2 + solid performance undertow (4.6MB)
Spring Boot migration tomcat
Reactive event-loop vert.x or grizzly
Jakarta EE (JDK 17+) jetty-jakarta / undertow-jakarta
Standalone WebSocket solon-server-websocket (0.4MB)

The server is infrastructure. The framework is your code. Solon keeps them separate — and that's a good thing.