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.
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.
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:
Selector notifies
workers when connections are readyThe 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.
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 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.
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.
The architecture decouples transport I/O from protocol logic.
Endpoint is the transport-agnostic I/O interface, providing:
send(ByteBuffer) - queues data for transmissionstartTLS() - initiates STARTTLS upgradeclose() - closes the endpointisOpen() - tests whether the endpoint is activegetRemoteAddress(), getLocalAddress() - peer
addressespauseRead(), resumeRead() - flow control for
incoming dataonWriteReady(Runnable) - callback when the write buffer has
drained
ProtocolHandler is the callback interface for protocol logic:
connected(Endpoint) - called when the endpoint is readyreceive(Endpoint, ByteBuffer) - called when data arrivessecurityEstablished(SecurityInfo) - called after TLS
handshakedisconnected(Endpoint) - called on close
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.
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.
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.
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.
Gumdrop provides ready-to-use service implementations for common use cases:
WebDAVService serves static files over HTTP
from a filesystem root with optional WebDAV (RFC 4918) distributed authoring support
(PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK)ServletService hosts JSP and
servlet applications with full Servlet API support, clustering, and deploymentSimpleFTPService (filesystem root
with read-only option), RoleBasedFTPService (role-based access control
with quota management), and AnonymousFTPService (anonymous read-only
public distribution)LocalDeliveryService for mailbox delivery,
SimpleRelayService for MX forwardingDefaultIMAPService accepts all operations
using the configured Realm and MailboxFactoryDefaultPOP3Service accepts all operations
using the configured Realm and MailboxFactoryDNSService provides a DNS proxy with caching
and custom resolution; subclass for custom name resolution logicDefaultMQTTService provides a complete
MQTT 3.1.1/5.0 message broker; accepts all connections, publishes, and
subscriptions using the configured RealmDefaultSOCKSService provides a SOCKS4/4a/5
proxy with destination filtering and relay limits; optional Realm for SOCKS5
authenticationThese services handle the common case while serving as examples for custom implementations. In many deployments, the default services with appropriate configuration are sufficient.
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.
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>
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.
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().
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.
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.
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