About PLC4J

PLC4J is a sub-project of PLC4X with implementations based on Java targeted at runtimes for Java 1.8 or above.

One of PLC4X’s core principals is, that an application using PLC4X should be independent of the PLC or protocol being used.

When addressing a resource on a remote there are two parts that are dependent on the protocol and the type of PLC:

  • Addressing the PLC itself

  • Addressing a resource on the PLC

Providing this independence to addressing the PLC itself is completely handled by the PlcDriverManager the application requests a connection from.

Hereby the design of the url string passed to the getConnection method is greatly inspired by JDBC.

The protocol prefix of the url specifies the type of driver being used.

For example, when connecting to a Siemens PLC using the S7/Step7 Protocol, the url: s7:// causes the driver manager to create a S7 connection instance. The part behind the : is hereby used by the driver implementation to configure that particular connection.

For a S7 connection, for example, this is IP address/host name/rack number/slot number. For different types of connections this url structure will greatly differ.

As mentioned above, the second platform dependent information is the address of resources on a PLC. The format of an address greatly depends on the type of connection. Therefore parseAddress is one of the only methods defined in the PlcConnection interface any connection has to implement.

This method returns an object implementing the Address interface which then can be used by the same connection to identify remote resources.

plc4x architecture


Below code example connects to a remote Siemens S7 PLC using the S7/Step7 protocol and then reads the state of the inputs and outputs from this.

It also demonstrates two ways this information can be accessed:

  • Synchronously (The application pauses, till the response is received)

  • Asynchronously (The application continues and can

package org.apache.plc4x.java.examples.helloplc4x;

import org.apache.plc4x.java.PlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.value.PlcValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;

public class HelloPlc4x {

    private static final Logger logger = LoggerFactory.getLogger(HelloPlc4x.class);

     * Example code do demonstrate using PLC4X.
     * @param args ignored.
    public static void main(String[] args) throws Exception {
        CliOptions options = CliOptions.fromArgs(args);
        if (options == null) {
            // Could not parse.

        // Establish a connection to the plc using the url provided as first argument
        try (PlcConnection plcConnection = new PlcDriverManager().getConnection(options.getConnectionString())) {

            // Check if this connection support reading of data.
            if (!plcConnection.getMetadata().canRead()) {
                logger.error("This connection doesn't support reading.");

            // Create a new read request:
            // - Give the single item requested the alias name "value"
            PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();
            for (int i = 0; i < options.getFieldAddress().length; i++) {
                builder.addItem("value-" + i, options.getFieldAddress()[i]);
            PlcReadRequest readRequest = builder.build();

            // Read synchronously ...
            // NOTICE: the ".get()" immediately lets this thread pause until
            // the response is processed and available.
            logger.info("Synchronous request ...");
            PlcReadResponse syncResponse = readRequest.execute().get();
            // Simply iterating over the field names returned in the response.

            PlcValue asPlcValue = syncResponse.getAsPlcValue();

            // Read asynchronously ...
            // Register a callback executed as soon as a response arrives.
            logger.info("Asynchronous request ...");
            CompletionStage<? extends PlcReadResponse> asyncResponse = readRequest.execute();
            asyncResponse.whenComplete((readResponse, throwable) -> {
                if (readResponse != null) {
                } else {
                    logger.error("An error occurred: " + throwable.getMessage(), throwable);

            // Give the async request a little time...
        } catch (Exception e) {

    private static void printResponse(PlcReadResponse response) {
        for (String fieldName : response.getFieldNames()) {
            if(response.getResponseCode(fieldName) == PlcResponseCode.OK) {
                int numValues = response.getNumberOfValues(fieldName);
                // If it's just one element, output just one single line.
                if(numValues == 1) {
                    logger.info("Value[" + fieldName + "]: " + response.getObject(fieldName));
                // If it's more than one element, output each in a single row.
                else {
                    logger.info("Value[" + fieldName + "]:");
                    for(int i = 0; i < numValues; i++) {
                        logger.info(" - " + response.getObject(fieldName, i));
            // Something went wrong, to output an error message instead.
            else {
                logger.error("Error[" + fieldName + "]: " + response.getResponseCode(fieldName).name());