Lifecycle-aware serial port connectivity for GuicedEE applications using jSerialComm and Vert.x 5.
Inject @Named CerialPortConnection singletons by port number, configure with CRTP-fluent setters, and let the framework handle connection lifecycle, idle monitoring, automatic reconnect with exponential backoff, MicroProfile Health reporting, and optional OpenTelemetry tracing.
Built on jSerialComm · Vert.x · Google Guice · MicroProfile Health · JPMS module com.guicedee.cerial · Java 25+
<dependency>
<groupId>com.guicedee</groupId>
<artifactId>cerial</artifactId>
</dependency>Gradle (Kotlin DSL)
implementation("com.guicedee:cerial:2.0.0-SNAPSHOT")- Guice-managed port singletons —
CerialPortsBindingspre-binds ports 0–999 as@Named("0")through@Named("999")singletons; inject by port number - CRTP-fluent configuration — chain
setBaudRate(),setDataBits(),setParity(),setStopBits(),setFlowControl()and callconnect()— all methods returnthis - Idle monitoring —
CerialIdleMonitoruses a Vert.x periodic timer to detect silent connections and update status toSilent - Automatic reconnect — exponential backoff reconnect (1 s → 60 s cap) via Vert.x timers on connection failure or port loss
- Status lifecycle — rich
ComPortStatusenum with 12 states (Offline,Silent,Running,Missing,Failed, etc.) and groupedEnumSethelpers - Message-delimited reads —
DataSerialPortMessageListeneruses jSerialComm'sSerialPortMessageListenerfor delimiter-based framing (default:\n) - Byte-level reads —
DataSerialPortBytesListenerfor raw byte-array reception - Per-port logging — each connection gets its own Log4j2 rolling file logger under
cerial/ - Health check —
CerialHealthCheckimplements@Liveness,@Readiness,@Startupand reports status of all active connections via the health module - Connection registry —
CerialConnectionRegistrytracks all active connections for health and lifecycle management - OpenTelemetry tracing — optional
@Tracespans onconnect(),disconnect(), read, and write withserial.port,serial.bytes_read, andserial.bytes_writtenmetrics (requiresguiced-telemetry) - Cross-platform — COM ports on Windows (
COM1), USB serial on Linux (/dev/ttyUSB0) - JSpecify nullability —
@NonNullannotations on all public fluent setters - JSON serializable —
CerialPortConnectionimplementsIJsonRepresentationwith Jackson annotations - Graceful shutdown —
IGuicePreDestroyintegration closes all open ports when the context tears down
Step 1 — Inject a named connection by port number:
@Inject
@Named("1")
private CerialPortConnection connection;Step 2 — Configure and connect:
connection.setBaudRate(BaudRate.$9600)
.setDataBits(DataBits.$8)
.setParity(Parity.None)
.setStopBits(StopBits.$1)
.setFlowControl(FlowControl.None)
.connect();Step 3 — Send and receive data:
// Write
connection.write("Hello, device!");
// Read (callback on Vert.x worker thread)
connection.setComPortRead((data, port) -> {
String message = new String(data).trim();
System.out.println("Received: " + message);
});Step 4 — Monitor status changes:
connection.onComPortStatusUpdate((conn, status) -> {
System.out.println("Port " + conn.getComPort() + " → " + status);
});No JPMS provides declarations are needed for consuming code — the module is registered automatically via its own IGuiceModule provider.
IGuiceContext.instance()
└─ Guice injector created
└─ CerialPortsBindings.configure()
└─ Bind @Named("0")..@Named("999") → CerialPortConnectionProvider (Singleton)
└─ First injection of @Named("N") CerialPortConnection
└─ CerialPortConnectionProvider.get()
├─ new CerialPortConnection(N, BaudRate.$9600)
├─ SerialPort.getCommPort("COMN")
├─ DataSerialPortMessageListener created
├─ CerialIdleMonitor created
├─ CerialConnectionRegistry.register(this)
└─ Register as IGuicePreDestroy
└─ connection.connect()
├─ beforeConnect() → Guice injects members, configure port parameters
├─ SerialPort.openPort()
├─ afterConnect() → attach data listener, start idle monitor
├─ Register shutdown hook
└─ Status → Silent
ComPortStatus provides 12 states with UI metadata (icon, background/foreground classes):
| Status | Meaning | Group |
|---|---|---|
Offline |
Port is closed or disconnected | Exception |
Missing |
Port not detected on the system | Exception |
Failed |
Connection attempt failed | Exception |
InUse |
Port claimed by another process | Exception |
GeneralException |
Unhandled error | Exception |
Opening |
Port is initializing | Transitional |
OperationInProgress |
Long-running operation active | Transitional |
FileTransfer |
Bulk data transfer underway | Transitional |
Silent |
Connected but idle | Active |
Logging |
Connected and logging telemetry | Active |
Running |
Connected and actively communicating | Active |
Simulation |
Running in simulated mode | Active |
Grouped EnumSet helpers: exceptionOperations, pauseOperations, portActive, portOffline, onlineServerStatus.
When a connection fails or is lost, scheduleReconnect() uses a Vert.x timer with exponential backoff:
- Starts at
initialReconnectDelaySeconds(default 1 s) - Doubles each attempt: 1 s → 2 s → 4 s → 8 s → …
- Capped at
maxReconnectDelaySeconds(default 60 s) - Resets on successful reconnect
connection.setInitialReconnectDelaySeconds(2)
.setMaxReconnectDelaySeconds(30);CerialIdleMonitor runs a Vert.x periodic timer (default every 120 s) that checks lastMessageTime. If idle beyond the threshold, the status transitions to Silent:
// Custom idle detection: check every 30s, idle after 60s
connection.setIdleTimerSeconds(60);
connection.setMonitor(new CerialIdleMonitor(connection, 2, 30, 60));Register an error callback for connection failures:
connection.setComPortError((throwable, conn, status) -> {
log.error("Port {} error: {} → {}", conn.getComPort(), throwable.getMessage(), status);
});If no error callback is set, the status is updated automatically and reconnect is scheduled.
| Method | Default | Values |
|---|---|---|
setBaudRate() |
BaudRate.$9600 |
$300, $600, $1200, $4800, $9600, $14400, $19200, $38400, $57600, $115200, $128000, $256000 |
setDataBits() |
DataBits.$8 |
$5, $6, $7, $8 |
setParity() |
Parity.None |
None, Odd, Even, Mark, Space |
setStopBits() |
StopBits.$1 |
$1, $1_5, $2 |
setFlowControl() |
FlowControl.None |
None, RtsCtsIn, RtsCtsOut, XonXoffIn, XonXoffOut |
Set an overall flow type that configures the underlying jSerialComm flags:
connection.setFlow(FlowType.XONXOFF); // or FlowType.RTSCTS, FlowType.Noneconnection.setEndOfMessage(new char[]{'\r', '\n'});connection.setBufferSize(2048);CerialHealthCheck is a @Liveness, @Readiness, @Startup MicroProfile Health check that reports the status of all active connections tracked by CerialConnectionRegistry:
{
"status": "UP",
"checks": [{
"id": "com.guicedee.cerial.CerialHealthCheck",
"status": "UP",
"data": {
"COM1": "Running",
"COM3": "Silent"
}
}]
}A connection is DOWN if its status is Offline, Missing, GeneralException, Failed, or InUse. If no active connections exist, the check returns UP with "No active connections".
When guiced-telemetry is on the classpath, the module automatically:
- Creates
serial.bytes_writtenandserial.bytes_readcounters via the OpenTelemetryMeter - Traces
connect()anddisconnect()calls with@Traceand@SpanAttribute - Traces write operations via
CerialWriteTracerwithserial.port,serial.portNumber,serial.message_lengthspan attributes - Traces read operations via
CerialDataTracerwithserial.data,serial.port,serial.message_lengthspan attributes
All telemetry dependencies are requires static — they are completely optional.
Each connection creates a dedicated Log4j2 rolling file logger:
| Port | Logger name | File |
|---|---|---|
| COM1 | COM1 |
cerial/COM1.log |
| COM3 | COM3 |
cerial/COM3.log |
| (none) | cerial |
cerial/cerial.log |
Log format: [yyyy-MM-dd HH:mm:ss.SSS] [LEVEL] - [message]
Messages use emoji prefixes for quick scanning:
📤 TX— data sent📥 RX— data received🚀— port opening✅— success❌— error⚠️— disconnect/warning🔄— reconnect scheduled🔌— reconnect attempt
com.guicedee.cerial
├── com.guicedee.guicedinjection (GuicedEE runtime — scanning, Guice, lifecycle)
├── com.guicedee.client (GuicedEE SPI contracts — IGuicePreDestroy, IGuiceModule)
├── com.guicedee.jsonrepresentation (JSON serialization — IJsonRepresentation)
├── com.fazecast.jSerialComm (jSerialComm — serial port I/O)
├── io.vertx.core (Vert.x — timers for idle monitor and reconnect)
├── org.apache.commons.lang3 (Commons Lang — utility classes)
├── org.apache.commons.io (Commons IO)
├── org.apache.logging.log4j (Log4j2 — per-port rolling loggers)
├── com.guicedee.health (optional — MicroProfile Health integration)
└── com.guicedee.telemetry (optional — OpenTelemetry tracing)
Module name: com.guicedee.cerial
The module:
- exports
com.guicedee.cerial,com.guicedee.cerial.enumerations,com.guicedee.cerial.implementations - provides
IGuiceModulewithCerialPortsBindings - requires static
com.guicedee.health(optional health check integration) - requires static
com.guicedee.telemetry(optional OpenTelemetry tracing)
| Class | Role |
|---|---|
CerialPortConnection<J> |
Core CRTP-fluent connection — configure, connect, write, receive, lifecycle |
CerialPortsBindings |
IGuiceModule — binds @Named("0")..@Named("999") to CerialPortConnectionProvider singletons |
CerialPortConnectionProvider |
Guice Provider — creates CerialPortConnection with default baud rate |
CerialConnectionRegistry |
Thread-safe registry of all active connections |
CerialIdleMonitor |
Vert.x periodic timer that detects idle connections |
CerialHealthCheck |
@Liveness + @Readiness + @Startup health check for all active connections |
CerialDataReceived |
Functional interface (BiConsumer<byte[], CerialPortConnection>) for read callbacks |
DataSerialPortMessageListener |
jSerialComm SerialPortMessageListener — delimiter-based message framing |
DataSerialPortBytesListener |
jSerialComm SerialPortDataListener — raw byte-array reception |
ComPortEvents |
SPI contract for read callbacks shared by message and byte listeners |
CerialDataTracer |
OpenTelemetry @Trace wrapper for read operations |
CerialWriteTracer |
OpenTelemetry @Trace wrapper for write operations |
SerialPortException |
RuntimeException for serial port errors |
| Enum | Values |
|---|---|
BaudRate |
$300 – $256000 (12 rates) |
DataBits |
$5, $6, $7, $8 |
Parity |
None, Odd, Even, Mark, Space |
StopBits |
$1, $1_5, $2 |
FlowControl |
None, RtsCtsIn, RtsCtsOut, XonXoffIn, XonXoffOut |
FlowType |
None, XONXOFF, RTSCTS |
ComPortStatus |
12 states with UI metadata (icon, CSS classes) |
ComPortType |
Device, etc. |
Issues and pull requests are welcome — please include the serial port, OS, jSerialComm version, and log excerpt. Follow CRTP (no builders) and JSpecify nullness conventions.