DEV Community

Solon Framework
Solon Framework

Posted on

Solon's 10 HTTP Servers: Pick Your Engine With One Dependency Change

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.

Top comments (3)

Collapse
 
merbayerp profile image
Mustafa ERBAY

I like the separation between the framework and the transport layer. Swapping the server with a dependency change is a clean architectural boundary.

One thing I’d still encourage is benchmarking and compatibility testing before treating the engines as interchangeable. Different servers can have different threading models, timeout defaults, TLS behavior, WebSocket implementations, and performance characteristics under load. The application code may stay identical, but the operational profile often doesn’t.

That abstraction is a real strength—as long as teams validate the runtime characteristics before switching engines in production.

Collapse
 
solonjava profile image
Solon Framework

Thanks, Mustafa! That is exactly the design philosophy behind Solon — the framework owns the routing layer and the IoC container, while the HTTP server is just an adapter. It also makes testing easier: you can spin up the same app with jdkhttp locally and Undertow in production, same codebase, zero surprises. Glad you like it!

Collapse
 
merbayerp profile image
Mustafa ERBAY

Thanks! I completely agree with the architectural separation. I’d just be a little careful with the phrase “zero surprises.” 😊 Zero code changes, absolutely. Zero operational differences, probably not. That’s actually one of the strengths of the design—you can benchmark the same application across different engines without changing the business logic, then choose the one whose runtime characteristics best fit your workload.