Core Concepts
Server Configuration
Soklet applications do not deploy on traditional application servers like Jetty or Tomcat. There is no concept of a Servlet Container or a WAR file (although Soklet does offer Servlet integration for legacy code).
Instead, Soklet provides its own HTTP server out-of-the-box: an embedded version of Elliot Barlas' Microhttp in the form of a Server implementation accessible via the Server::withPort builder, which listens to HTTP 1.1 on the port you specify.
Configuring The Default Server
The minimum required Server configuration is the port number on which to listen.
Soklet will pick sensible defaults, shown below, for other settings.
// The only required configuration is port number
Server server = Server.withPort(8080 /* port */)
// Host on which we are listening
.host("0.0.0.0")
// The number of connection-handling event loops to run concurrently.
// You likely want the number of CPU cores as per below
.concurrency(Runtime.getRuntime().availableProcessors())
// How long to permit a request to process before timing out
.requestTimeout(Duration.ofSeconds(60))
// How long to block waiting for the socket's channel to become ready.
// If zero, block indefinitely
.socketSelectTimeout(Duration.ofMillis(100))
// How long to wait for request handler threads to complete on shutdown
.shutdownTimeout(Duration.ofSeconds(5))
// The biggest request we permit clients to make (10 MB)
.maximumRequestSizeInBytes(1_024 * 1_024 * 10)
// Requests are read into a byte buffer of this size.
// Adjust down if you expect tiny requests.
// Adjust up if you expect larger requests.
.requestReadBufferSizeInBytes(1_024 * 64)
// The maximum number of pending connections on the socket
// (values < 1 use JVM platform default)
.socketPendingConnectionLimit(0)
// Vend an ExecutorService that is used to service HTTP requests.
// For Virtual Threads: you likely want 1 virtual thread per request.
// For Native Threads: you likely want a fixed-size pool.
.requestHandlerExecutorServiceSupplier(() ->
Executors.newVirtualThreadPerTaskExecutor()
)
// Provide your own 'multipart/form-data' parser if you'd like
// to override the default behavior of MultipartParser::defaultInstance
.multipartParser(new MultipartParser() {
@Nonnull
@Override
public Map<String, Set<MultipartField>> extractMultipartFields(
@Nonnull Request request
) {
// TODO: examine request and return multipart fields
return Map.of();
}
})
.build();
// Use our custom server
SokletConfig config = SokletConfig.withServer(server)
// Not shown: other Soklet builder customizations
.build();
// Start it up
try (Soklet soklet = Soklet.withConfig(config)) {
soklet.start();
System.out.println("Soklet started, press [enter] to exit");
soklet.awaitShutdown(ShutdownTrigger.ENTER_KEY);
}
// The only required configuration is port number
Server server = Server.withPort(8080 /* port */)
// Host on which we are listening
.host("0.0.0.0")
// The number of connection-handling event loops to run concurrently.
// You likely want the number of CPU cores as per below
.concurrency(Runtime.getRuntime().availableProcessors())
// How long to permit a request to process before timing out
.requestTimeout(Duration.ofSeconds(60))
// How long to block waiting for the socket's channel to become ready.
// If zero, block indefinitely
.socketSelectTimeout(Duration.ofMillis(100))
// How long to wait for request handler threads to complete on shutdown
.shutdownTimeout(Duration.ofSeconds(5))
// The biggest request we permit clients to make (10 MB)
.maximumRequestSizeInBytes(1_024 * 1_024 * 10)
// Requests are read into a byte buffer of this size.
// Adjust down if you expect tiny requests.
// Adjust up if you expect larger requests.
.requestReadBufferSizeInBytes(1_024 * 64)
// The maximum number of pending connections on the socket
// (values < 1 use JVM platform default)
.socketPendingConnectionLimit(0)
// Vend an ExecutorService that is used to service HTTP requests.
// For Virtual Threads: you likely want 1 virtual thread per request.
// For Native Threads: you likely want a fixed-size pool.
.requestHandlerExecutorServiceSupplier(() ->
Executors.newVirtualThreadPerTaskExecutor()
)
// Provide your own 'multipart/form-data' parser if you'd like
// to override the default behavior of MultipartParser::defaultInstance
.multipartParser(new MultipartParser() {
@Nonnull
@Override
public Map<String, Set<MultipartField>> extractMultipartFields(
@Nonnull Request request
) {
// TODO: examine request and return multipart fields
return Map.of();
}
})
.build();
// Use our custom server
SokletConfig config = SokletConfig.withServer(server)
// Not shown: other Soklet builder customizations
.build();
// Start it up
try (Soklet soklet = Soklet.withConfig(config)) {
soklet.start();
System.out.println("Soklet started, press [enter] to exit");
soklet.awaitShutdown(ShutdownTrigger.ENTER_KEY);
}
Virtual Threads
The default configuration will transparently use Virtual Threads if available at runtime (JDK 19 or 20 with the --enable-preview flag or JDK 21+ stock configuration) and fall back to native threads if not.
If you prefer not to use Virtual Threads, provide your own ExecutorService to requestHandlerExecutorServiceSupplier as shown above.

