How to Connect Java to Industrial Devices: Modbus, JNI, and Serial Port Solutions
This guide explains how Java can communicate with industrial hardware by using the Modbus protocol with jLibModbus, accessing low‑level registers via JNI, and employing serial‑port libraries such as JSerialComm or RXTX, providing step‑by‑step setup, code examples, dependency configuration, and practical considerations for reliable device integration.
Overview
The article demonstrates three practical ways for Java applications to interact with industrial equipment: using the Modbus protocol through the jLibModbus library, calling native code via Java Native Interface (JNI), and performing serial‑port communication with libraries such as JSerialComm or RXTX. Each method includes dependency setup, sample code, and usage tips.
Modbus TCP with jLibModbus
Modbus is a widely adopted protocol for industrial automation. The example uses the Modbus TCP variant to read holding registers from a device at IP 192.168.1.100 (default port 502).
Implementation Steps
Add the jLibModbus dependency to pom.xml:
<dependency>
<groupId>com.intelligt.modbus</groupId>
<artifactId>jlibmodbus</artifactId>
<version>1.2.8.1</version>
</dependency>Configure a TcpParameters object with the device IP and port.
Create a ModbusMaster via ModbusMasterFactory.createModbusMasterTCP, connect, and read registers using readHoldingRegisters.
Sample Code
import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.ModbusMaster;
import com.intelligt.modbus.jlibmodbus.ModbusMasterFactory;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import java.net.InetAddress;
public class ModbusTcpClient {
public static void main(String[] args) {
try {
TcpParameters tcpParameters = new TcpParameters();
InetAddress address = InetAddress.getByName("192.168.1.100");
tcpParameters.setHost(address);
tcpParameters.setPort(Modbus.TCP_PORT);
ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
master.connect();
int slaveId = 1;
int startAddress = 0;
int quantity = 10;
try {
int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity);
System.out.println("Register data:");
for (int i = 0; i < registerValues.length; i++) {
System.out.println("Register[" + (startAddress + i) + "] = " + registerValues[i]);
}
} catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) {
System.err.println("Modbus read failed: " + e.getMessage());
}
master.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}The code prints each register value and handles protocol, number, and I/O exceptions. Adjust the IP, port, slave ID, start address, and quantity to match your device.
JNI (Java Native Interface)
JNI enables Java to invoke native C/C++ functions, which is useful when direct hardware access or high‑performance operations are required.
Typical JNI Workflow
Declare a native method in a Java class.
Compile the Java class with javac.
Generate a C header using javah (or javac -h in newer JDKs).
Implement the native method in C/C++.
Compile the native code into a shared library ( .so on Linux/macOS, .dll on Windows).
Load the library in Java with System.loadLibrary and call the native method.
Example
public class RegisterReader {
public native int readRegister(int address);
static { System.loadLibrary("register_reader"); }
public static void main(String[] args) {
RegisterReader r = new RegisterReader();
int value = r.readRegister(0x1000);
System.out.println("Register Value: " + value);
}
}Corresponding C header ( RegisterReader.h) and implementation ( RegisterReader.c) define Java_RegisterReader_readRegister, which can perform memory‑mapped register reads or call vendor‑provided driver APIs. The shared library is built with a command such as:
gcc -shared -o libregister_reader.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux RegisterReader.cRunning the Java program with -Djava.library.path=. loads the native library and prints the register value.
Serial‑Port Communication (JSerialComm / RXTX)
When devices expose RS232/RS485 interfaces, Java can communicate via serial‑port libraries. JSerialComm is actively maintained and cross‑platform, while RXTX is older and less reliable.
JSerialComm Example
import com.fazecast.jSerialComm.SerialPort;
public class SerialCommExample {
public static void main(String[] args) {
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("Available ports:");
for (int i = 0; i < ports.length; i++) {
System.out.println(i + ": " + ports[i].getSystemPortName());
}
SerialPort serialPort = ports[0]; // choose first port
serialPort.setBaudRate(9600);
serialPort.setNumDataBits(8);
serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT);
serialPort.setParity(SerialPort.NO_PARITY);
if (serialPort.openPort()) {
System.out.println("Port opened: " + serialPort.getSystemPortName());
} else {
System.out.println("Failed to open port");
return;
}
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
String command = "READ_REGISTER"; // device‑specific command
serialPort.writeBytes(command.getBytes(), command.length());
byte[] buffer = new byte[1024];
int numRead = serialPort.readBytes(buffer, buffer.length);
System.out.println("Read " + numRead + " bytes");
for (int i = 0; i < numRead; i++) {
System.out.print((char) buffer[i]);
}
System.out.println();
serialPort.closePort();
System.out.println("Port closed");
}
}The program lists ports, configures communication parameters, sends a placeholder command, reads the response, and closes the port.
RXTX Example (Legacy)
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;
public class RXTXExample {
public static void main(String[] args) throws Exception {
CommPortIdentifier pid = CommPortIdentifier.getPortIdentifier("COM3");
SerialPort sp = (SerialPort) pid.open("SerialComm", 2000);
sp.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
InputStream in = sp.getInputStream();
OutputStream out = sp.getOutputStream();
out.write("READ_REGISTER".getBytes());
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println("Read " + len + " bytes");
for (int i = 0; i < len; i++) System.out.print((char) buf[i]);
System.out.println();
sp.close();
}
}RXTX requires native binaries and is less convenient than JSerialComm, but the code flow mirrors the JSerialComm example.
Choosing the Right Approach
JNI : Best for ultra‑low latency or when a device driver is only available as native C/C++ code.
Modbus : Ideal for standard industrial equipment that supports Modbus RTU or TCP; provides a uniform way to read/write registers across many devices.
Serial‑Port Libraries (JSerialComm / RXTX) : Suitable for custom or legacy hardware that communicates over RS232/RS485 with proprietary protocols.
Consider the device’s communication protocol, performance requirements, and development resources when selecting a method.
Conclusion
Java can reliably interact with industrial hardware through three complementary techniques: Modbus TCP/RTU via jLibModbus, native calls through JNI for direct register access, and serial‑port communication using JSerialComm or RXTX. Proper dependency configuration, error handling, and protocol‑specific settings are essential for stable integration.
Java Architecture Stack
Dedicated to original, practical tech insights—from skill advancement to architecture, front‑end to back‑end, the full‑stack path, with Wei Ge guiding you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
