Gumdrop

gumdrop

Congratulations, you've installed Gumdrop!

Gumdrop is a multipurpose Java server framework using asynchronous, non-blocking, event-driven I/O. It provides a generic, extensible server architecture with transparent TLS support, and includes implementations for HTTP, SMTP, IMAP, POP3, FTP, DNS, MQTT, and SOCKS proxy protocols.

Documentation

Architecture

Gumdrop uses an event-driven, non-blocking I/O architecture first developed in 2005 based on the then-new Java NIO Selector API. This architecture was the original motivator for the project at a time when mainstream enterprise servlet containers still used blocking java.net.Socket implementations that directly tied the number of incoming connections to JVM threads, preventing scalability.

Gumdrop Architecture Diagram

Asynchronous vs Synchronous

Traditional synchronous servers allocate one thread per connection. A thread blocks on socket read, processes the request, blocks on socket write, and repeats. This model is simple but has fundamental scalability limitations:

Gumdrop's asynchronous architecture inverts this model. A small, fixed pool of worker threads (default: 2× CPU cores) handles I/O for all connections:

Event-Driven Model

The event-driven model is particularly suited to modern distributed enterprise systems where services communicate over HTTP, message queues, and databases. These systems are inherently I/O-bound: services spend most time waiting for network responses. An event-driven server maximises throughput by never waiting—when one connection is idle, the worker immediately serves another.

SelectorLoop Architecture

A single AcceptSelectorLoop thread handles accepting new connections on all server ports. When a connection is accepted, the resulting endpoint is assigned to one of a pool of worker SelectorLoop threads using round-robin distribution. Each endpoint is bound to a single worker for its entire lifetime, eliminating race conditions and simplifying state management.

Gumdrop Class Hierarchy

Servers: Services and Listeners

Gumdrop separates what a server does from where it listens. The Service interface represents a transport-agnostic unit of application logic — the overall service being provided, such as HTTP, FTP, or SMTP. A service owns one or more Listeners, which are the transport endpoints that bind to ports (or UNIX sockets) and accept connections.

Services define protocol behaviour, authentication, and routing. Listeners define the transport binding: port, address, TLS configuration, and which transport layer to use (TCP, UDP, or QUIC via createTransportFactory()). A single service may have multiple listeners — for example, an SMTP service might listen on port 25 (cleartext) and port 465 (implicit TLS), or on both TCP and a UNIX domain socket for local delivery.

Listeners can be static (declared in configuration) or dynamic (created at runtime). FTP illustrates dynamic listeners: when a client issues PASV or EPSV, the protocol handler creates a new listener for the data connection, registers it with the service via addDynamicListener(), and unregisters it when the transfer completes. The service lifecycle ensures all listeners — static and dynamic — are stopped on shutdown.

When the AcceptSelectorLoop accepts an incoming connection on a listener, it calls the listener's createHandler() factory to obtain a ProtocolHandler. Protocol-specific listeners (e.g., SMTPListener, FTPListener) typically delegate to their owning service's createHandler(Listener) so that handler selection can vary by listener (e.g., different policies for submission vs. relay ports).

TLS configuration (keystore, password, format, client authentication, ALPN), CIDR filtering, and rate limiting are set as properties on Listener and delegated to the TransportFactory.

Clients

ClientEndpoint launches outbound connections using the same event-driven infrastructure as servers. A ClientHandler receives lifecycle callbacks (connected, received data, security established, disconnected). ClientEndpointPool manages pooled outbound endpoints with SelectorLoop affinity.

When a client endpoint is created within a server context (e.g., an SMTP relay forwarding mail), it can be assigned to the same SelectorLoop as the originating server endpoint. This SelectorLoop affinity provides:

Performing all your hot path operations on the selector thread is highly efficient and scalable but very sensitive to blocking operations. It is for this reason that Gumdrop provides asynchronous, non-blocking clients and parsing components for all the operations you are likely to need within your service implementation.

Endpoints and Handlers

The architecture decouples transport I/O from protocol logic. Endpoint is the transport-agnostic I/O interface, providing:

ProtocolHandler is the callback interface for protocol logic:

Protocol implementations (HTTP, SMTP, IMAP, etc.) implement ProtocolHandler rather than extending a base class. LineParser replaces the former LineBasedConnection for line-oriented protocols, using composition instead of inheritance. The framework handles read/write buffering, TLS encryption/decryption, and endpoint lifecycle.

Flow Control

The Endpoint interface provides transport-level flow control that gives protocol handlers explicit control over the rate of data transfer. This is a simpler, more efficient alternative to the Reactive Streams specification: instead of introducing Publisher/Subscriber abstractions and demand-based signalling, Gumdrop exposes three primitives directly on the transport — pauseRead(), resumeRead(), and onWriteReady(Runnable) — that manipulate the underlying I/O interest set. This provides complete backpressure with zero object allocation overhead and no additional threading.

On TCP endpoints, pauseRead() clears the OP_READ interest on the SelectionKey, causing the selector to stop delivering data from that connection. The kernel TCP receive buffer fills, the TCP window shrinks, and the remote peer naturally slows down. Calling resumeRead() reverses this. For writes, onWriteReady(Runnable) registers a one-shot callback that fires when the transport has drained its write buffer, allowing protocol handlers to feed data in chunks without unbounded memory growth. All TCP-based protocols (SMTP, IMAP, POP3, FTP) and their client counterparts use these primitives for large transfers such as message retrieval, uploads, and file downloads.

Staged Handler Pattern

Stateful protocols such as SMTP, IMAP, and POP3 present a particular challenge: tracking the current protocol state and understanding what operations are valid at any given moment. This difficulty is compounded by event-driven architectures like Gumdrop, where the complexity of asynchronous callbacks can be bewildering.

Gumdrop addresses this with a staged handler pattern that compartmentalises protocol processing into discrete stages, each with clear goals and decisions:

For example, an SMTP handler progresses through well-defined stages:

ClientConnected → HelloHandler → MailFromHandler → RecipientHandler → MessageDataHandler

At each stage, the handler receives protocol events (EHLO, MAIL FROM, RCPT TO) and uses the provided state interface to accept, reject, or make policy decisions. The protocol handler manages all protocol mechanics — SASL authentication challenges, TLS negotiation, command parsing — leaving the service to focus on business logic. Services need not be aware of the underlying transport.

Services and Clients

The same pattern works for both services (server side) and clients. An SMTP client handler receives events from the server (greeting, EHLO response, authentication challenges) and uses state interfaces to drive the conversation forward. This symmetry simplifies relay implementations where a server connection drives a client connection.

Default Services

Gumdrop provides ready-to-use service implementations for common use cases:

These services handle the common case while serving as examples for custom implementations. In many deployments, the default services with appropriate configuration are sufficient.

Datagram Facilities

For UDP-based protocols, UDPEndpoint (via UDPTransportFactory) provides the same event-driven model. The ProtocolHandler interface is reused: receive() delivers individual datagrams and sendTo() addresses responses to the source.

The DNS server demonstrates this abstraction, providing a complete DNS proxy that can be subclassed for custom name resolution. DTLS (TLS for UDP) is supported transparently, with per-remote-address session management handled automatically.

Transparent TLS

All endpoints support transparent TLS encryption. The framework handles:

To enable TLS, configure the keystore on any listener:

<service class="org.bluezoo.gumdrop.smtp.SMTPService">
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener" port="465" secure="true">
        <property name="keystore-file" path="keystore.p12"/>
        <property name="keystore-pass" value="secret"/>
    </listener>
</service>

UNIX Domain Sockets

Any TCP-based listener can also bind to a UNIX domain socket by specifying a path instead of a port. This uses the standard NIO UNIX domain socket support available since Java 16 (JEP 380). Once accepted, UNIX domain socket connections behave identically to TCP connections — the same SocketChannel, TCPEndpoint, and ProtocolHandler infrastructure is used unchanged.

UNIX domain sockets are useful for local inter-process communication where network overhead is unnecessary and filesystem permissions provide the security boundary. A common pattern is to expose a service on both TCP (for remote clients) and a UNIX socket (for local tools or reverse proxies):

<service class="org.bluezoo.gumdrop.smtp.LocalDeliveryService">
    <property name="mailbox-factory" ref="#mbox"/>
    <property name="local-domain" value="example.com"/>
    <!-- TCP listener for remote clients -->
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener"
              name="tcp" port="25"/>
    <!-- UNIX domain socket for local delivery -->
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener"
              name="unix" path="/var/run/smtp.sock"/>
</service>

The path and port attributes are mutually exclusive on a single listener element. Specifying both is a configuration error. Stale socket files from previous runs are automatically removed on bind, and the socket file is deleted on shutdown.

Endpoint Lifecycle

Servers provide configurable endpoint lifecycle management:

Timeouts can be configured with time unit suffixes (ms, s, m, h):

<service class="org.bluezoo.gumdrop.imap.IMAPService">
    <listener class="org.bluezoo.gumdrop.imap.IMAPListener">
        <property name="idle-timeout" value="30m"/>
        <property name="read-timeout" value="60s"/>
        <property name="connection-timeout" value="2m"/>
    </listener>
</service>

Endpoints track timestamps for creation, last activity, and connection establishment (after TLS handshake for secure endpoints). These are accessible via getTimestampCreated(), getTimestampLastActivity(), and getTimestampConnected().

Configuration

Gumdrop uses a lightweight dependency injection framework to wire together its components from an XML configuration file. See the Configuration documentation for full details on component declarations, property syntax (value, path, and ref attributes), type conversion, collections, services and listeners, and example configurations.

About the Logo

The Gumdrop logo is a gumdrop torus, generated using POV-Ray. A gumdrop torus is a mathematical construct—the logo is such a torus viewed from an angle that makes it resemble the letter G. Images were created using POV-Ray and/or GIMP and are copyright 2005 Chris Burdess.

Where to go from here

Explore the protocol-specific documentation linked above, or examine the example configurations and test applications. If the speed, simplicity, and ease of configuration appeals to you, please consider getting involved and developing a new feature.

If you have any questions about Gumdrop, please direct them to Chris Burdess.

Thanks for trying out Gumdrop.

Gumdrop