How to Use SpringBoot and Modbus4j for Modbus TCP Data Reading
This article walks through setting up a SpringBoot project with the Modbus4j library to communicate with Modbus TCP devices, covering repository configuration, Maven dependencies, utility class implementation, single and batch reads, scheduled tasks, and troubleshooting common function‑code errors.
Modbus TCP Overview
Modbus was developed by MODICON in 1979 as an industrial field‑bus protocol. In 1996 Schneider introduced Modbus TCP, which runs over Ethernet using a master/slave model. The protocol defines four data objects—coils, discrete inputs, holding registers, and input registers—and supports ASCII, RTU, and TCP transport.
Java Modbus Libraries
Several open‑source Java libraries exist:
Jamod – a classic Java Modbus implementation.
ModbusPal – a Java project that simulates realistic Modbus slaves.
Modbus4j – a high‑performance, easy‑to‑use library from Serotonin Software that supports ASCII, RTU, TCP, and UDP as master or slave.
JLibModbus – another Java Modbus implementation using jSSC and RXTX for serial communication.
Project Setup
Add the Infinite Automation snapshot and release repositories to <repositories> in pom.xml:
<repositories>
<repository>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
<id>ias-snapshots</id>
<name>Infinite Automation Snapshot Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
</repository>
<repository>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
<id>ias-releases</id>
<name>Infinite Automation Release Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-release/</url>
</repository>
</repositories>Then add the Modbus4j dependency:
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
<version>3.0.3</version>
</dependency>Utility Class
Create Modbus4jUtils in package com.badao.demo.utils:
package com.badao.demo.utils;
import com.serotonin.modbus4j.*;
import com.serotonin.modbus4j.exception.*;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
public class Modbus4jUtils {
static ModbusFactory modbusFactory;
static {
if (modbusFactory == null) {
modbusFactory = new ModbusFactory();
}
}
public static ModbusMaster getMaster(String ip, int port) throws ModbusInitException {
IpParameters params = new IpParameters();
params.setHost(ip);
params.setPort(port);
// TCP master
ModbusMaster master = modbusFactory.createTcpMaster(params, false);
master.init();
return master;
}
public static Boolean readCoilStatus(ModbusMaster master, int slaveId, int offset)
throws ModbusTransportException, ErrorResponseException {
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
return master.getValue(loc);
}
public static Boolean readInputStatus(ModbusMaster master, int slaveId, int offset)
throws ModbusTransportException, ErrorResponseException {
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
return master.getValue(loc);
}
public static Number readHoldingRegister(ModbusMaster master, int slaveId, int offset, int dataType)
throws ModbusTransportException, ErrorResponseException {
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
return master.getValue(loc);
}
public static Number readInputRegisters(ModbusMaster master, int slaveId, int offset, int dataType)
throws ModbusTransportException, ErrorResponseException {
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
return master.getValue(loc);
}
public static BatchResults<Integer> batchRead(ModbusMaster master, BatchRead<Integer> batch)
throws ModbusTransportException, ErrorResponseException, ModbusInitException {
batch.setContiguousRequests(false);
return master.send(batch);
}
}Usage Example
Obtain a master instance with the IP and port from configuration:
ModbusMaster master = Modbus4jUtils.getMaster(modebustcpIp, port);Read a single coil:
Boolean coil = Modbus4jUtils.readCoilStatus(master, 1, 0); // slaveId=1, offset=0Perform a batch read of multiple coils defined in a configuration file:
BatchRead<Integer> batch = new BatchRead<>();
List<Locator> locatorList = locatorConfig.getLocatorList();
locatorList.forEach(l ->
batch.addLocator(l.getId(), BaseLocator.coilStatus(l.getSlaveId(), l.getOffset())));
BatchResults<Integer> results = Modbus4jUtils.batchRead(master, batch);
Boolean v1 = (Boolean) results.getValue(0);
Boolean v2 = (Boolean) results.getValue(1);
Boolean v3 = (Boolean) results.getValue(2);Scheduled Task
Integrate the read logic into a SpringBoot scheduled job:
@Configuration
@EnableScheduling
public class GetModbusTCPDataTask {
@Value("${modebustcp.ip}")
private String modebustcpIp;
@Value("${modebustcp.port}")
private Integer port;
@Autowired
private LocatorConfig locatorConfig;
@Scheduled(fixedRateString = "2000")
public void getData() throws ModbusInitException {
ModbusMaster master = Modbus4jUtils.getMaster(modebustcpIp, port);
BatchRead<Integer> batch = new BatchRead<>();
List<Locator> locatorList = locatorConfig.getLocatorList();
locatorList.forEach(l ->
batch.addLocator(l.getId(), BaseLocator.coilStatus(l.getSlaveId(), l.getOffset())));
try {
BatchResults<Integer> batchResults = Modbus4jUtils.batchRead(master, batch);
System.out.println(LocalDateTime.now());
System.out.println("valueOne:" + batchResults.getValue(0));
System.out.println("valueTwo:" + batchResults.getValue(1));
System.out.println("valueThree:" + batchResults.getValue(2));
} catch (ModbusTransportException | ErrorResponseException e) {
e.printStackTrace();
}
}
}Testing Results
Local test output (GIF) shows successful reads of coil values. Online testing with an RS485‑to‑Ethernet converter (default port 502, slaveId 2) also produces correct values, as illustrated by the screenshots.
Troubleshooting
If the application throws
com.serotonin.modbus4j.exception.ErrorResponseException: Illegal function, the cause is a mismatched function code. The Modbus Slave Definition may be set to function code 03 (Holding Register) while the code attempts to read coil status (function code 01). Adjust the function code in the slave definition to match the desired operation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
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.
