ModbusBridge
The Commander4j Modbus Bridge (util_modbusBridge) is a headless service that lets applications without Modbus/TCP client capability set and query the values of a remote Modbus server over a simple REST API. It connects to one Modbus server, polls a fixed list of named points (configured in XML), keeps the latest values in memory, and serves them over HTTP. It also provides a live web UI to view and change values, and a second page to tail the log.
Unlike the interactive Commander4j Modbus Client, the Bridge runs unattended: it auto-reconnects when the link drops and is packaged to run as an operating-system service. It reuses the same Modbus engine as the ModbusClient and is a natural companion to the ModbusServer.
If you are new to Modbus, the Modbus primer on the Modbus Client page explains the terms (client/server, unit ID, and the four data tables) used throughout this page.
Purpose
The Modbus Bridge is useful when:
- An application or script that cannot speak Modbus needs to read or write a PLC or other Modbus/TCP device over plain HTTP/JSON
- A remote Modbus server's values should be exposed on a network as named points rather than raw addresses
- An unattended, always-on bridge is needed that survives link drops and restarts as a managed service
- A browser-based view of live values, with inline editing and a live log, is wanted without installing a desktop tool
Source Code and Releases
The Modbus Bridge is open source and hosted on GitHub:
- Repository: https://github.com/C4J/ModbusBridge
- Releases (downloads): https://github.com/C4J/ModbusBridge/releases
Running the Modbus Bridge
The recommended way to install the Modbus Bridge is to download a native installer for your platform, either from the Downloads page or from the GitHub releases page. Native installers are provided for Windows, macOS and Linux. Each installer bundles its own Java 25 runtime, so no separate Java installation is required.
As a service
The native installer registers and starts the Bridge as an operating-system service — launchd on macOS, a Windows service, or a systemd / init unit on Linux. The service runs with its working directory set to the installation directory, so all the relative paths below resolve correctly. Stopping the service triggers an ordered shutdown so that teardown events still reach the log file.
Running from the jar
Alternatively the Bridge can be launched from its distribution folder:
./start_modbusBridge.sh(macOS/Linux) orstart_modbusBridge.cmd(Windows), orant runto launch from compiled classes, or- directly with Java:
java -Dlog4j2.shutdownHookEnabled=false -Dlog4j2.configurationFile=xml/config/log4j2.xml -jar modbusBridge.jar
modbusBridge.jar is a thin jar — it references the dependency jars in lib/ via its manifest, so it must be run from the project root with lib/, xml/, web/ and logs/ alongside it. Any Java 25 runtime on the PATH will do; a bundled JRE is not required when run this way.
The two -D system properties matter, and the start scripts set them for you:
-Dlog4j2.shutdownHookEnabled=false— lets the Bridge's own shutdown hook stop log4j2 last, so teardown events still reach the log file the tail page reads.-Dlog4j2.configurationFile=xml/config/log4j2.xml—log4j2.xmllives underxml/config/, not on the classpath, so log4j2 must be pointed at it explicitly.
Configuration
All configuration is in xml/config/config.xml:
<config>
<modbus>
<ip>127.0.0.1</ip>
<port>1502</port>
<id>1</id>
<pollIntervalMs>1000</pollIntervalMs>
</modbus>
<webserver>
<ip>0.0.0.0</ip>
<port>8080</port>
</webserver>
<points>
<point name="pump_run" kind="COIL" address="0"/>
<point name="tank_level" kind="HOLDING_REGISTER" address="100"/>
<point name="fault" kind="DISCRETE_INPUT" address="10"/>
<point name="flow_rate" kind="INPUT_REGISTER" address="30"/>
</points>
</config>
The <modbus> block points the Bridge at the remote Modbus server: its host <ip>, TCP <port> (the Modbus standard is 502; the example uses 1502 because ports below 1024 need root on macOS/Linux), the unit / slave <id> to address, and the <pollIntervalMs> between reads. The <webserver> block sets the bind address for the HTTP server (0.0.0.0 = all interfaces) and the port it listens on.
Each <point> gives a human-friendly name (how REST clients and the web UI refer to it), a Modbus data table kind, and a zero-based protocol address. Valid kinds are COIL, DISCRETE_INPUT, HOLDING_REGISTER and INPUT_REGISTER. Only coils and holding registers are writable — discrete inputs and input registers are read-only in the Modbus data model (there is no client-write function code for them).
Logging is configured in xml/config/log4j2.xml (rolling file at logs/modbusBridge.log, 10 MB cap, 5 generations). The Bridge's own classes log at debug; the Netty transport at warn.
REST API
| Method | Path | Behaviour |
|---|---|---|
GET |
/api/points |
All points with current values + connection status. |
GET |
/api/points/{name} |
One point's current value. 404 if unknown. |
PUT |
/api/points/{name} {"value": N} |
Write a coil (0/1, or true/false) or holding register (0–65535). 400 for read-only kinds or out-of-range; 503 if the server is unreachable.
|
GET |
/api/status |
Connection health: connected, target, unit id, poll interval, last-poll time, point count. |
GET |
/api/licence |
Third-party licence list (from lib/license/LicenseInfo.xml).
|
GET |
/api/log/tail ?lines=N |
Recent log lines (default 200, max 5000). |
GET |
/events |
Server-Sent Events stream of the full points payload, pushed on every change. |
GET |
/events/log |
Server-Sent Events stream of newly-appended log lines. |
A write is self-correcting, not optimistic: the Bridge writes the value, then immediately reads the point back from the server, so the response and cache reflect the server's actual state. When the link is down, reads return the last snapshot flagged "valid":false,"stale":true and writes return 503.
Examples:
curl http://localhost:8080/api/points curl -X PUT -H 'Content-Type: application/json' -d '{"value":1}' http://localhost:8080/api/points/pump_run curl -X PUT -H 'Content-Type: application/json' -d '{"value":4242}' http://localhost:8080/api/points/tank_level
Web UI
Open http://<host>:8080/ in a browser:
- Points (
index.html) — a live table of every configured point, updated by push (SSE) with no refresh needed. Writable points have an inline editor; type a value and click Set (or press Enter) to issue the PUT. Stale values (link down) are shown in red. A manual Refresh button is kept as a fallback. - Log (
log.html) — the recent log backlog followed live as new lines are written, with an auto-scroll toggle.WARN/ERRORlines are colour-coded. - Licences (
licence.html) — the third-party libraries and their licences rendered as a table (the underlying/api/licenceendpoint returns the same data as raw JSON).
Modbus Behaviour
- Transport: Modbus/TCP only (no serial / RTU, no TLS), reusing the ModbusClient's Modbus engine.
- The poll thread reads every configured point once per
pollIntervalMs. A read failure tears the connection down, flags all points stale, and the Bridge auto-reconnects with exponential backoff (1 → 2 → 4 → 8 … up to 30 s), resetting on a successful connect.
Troubleshooting
Address already in useon start — another process holds the web port (default 8080). Change<webserver><port>or free the port.- All points show stale /
/api/statussays not connected — the Modbus server is unreachable. Check<modbus><ip>/<port>, that the server is running, and that both ends use the same unit id (the server answers only its configured unit). The log shows the backoff retries. - Writes return 503 — the link is down at that moment; the Bridge is between reconnect attempts. Reads still return the last-known (stale) values.
- PUT returns 400 "read-only" — the named point is a
DISCRETE_INPUTorINPUT_REGISTER; those cannot be written by a Modbus client. - No log lines / empty log tail page — confirm
-Dlog4j2.configurationFile=xml/config/log4j2.xmlis set (the start scripts do this) and the process working directory is the project root, sologs/is created in the right place.
See Also
- ModbusClient — the interactive desktop Modbus client; the Bridge reuses its Modbus engine
- ModbusServer — the server-side companion tool, useful as a bench target for the Bridge
- AutoLab4j — uses the same Modbus stack to monitor a coil and trigger a labeller on a change of state
- SocketTest — a raw TCP/IP testing utility