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.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
How to Use SpringBoot and Modbus4j for Modbus TCP Data Reading

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=0

Perform 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpringBootIndustrial IoTModbus TCPModbus4j
The Dominant Programmer
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.