Skip to content

First Client

Kevin Herron edited this page Oct 5, 2026 · 10 revisions

Applies to Milo 1.2.0-SNAPSHOT. Source baseline 7faaf4f05, 2026-10-04.

This program connects to the thermostat server from Your first server, reads its Temperature Variable, checks the result, and disconnects. Complete Getting started first and leave FirstServer running. The client uses anonymous identity and security policy None to match the loopback tutorial endpoint. For a secured deployment, see Client security.

What the client reads

On the server, Temperature is a component of the Thermostat Object, but the client does not need that structure to read it. A NodeId identifies a Node directly, wherever it sits in the hierarchy, so the program builds Temperature's NodeId from the namespace URI urn:eclipse:milo:wiki and the String identifier Temperature and reads it. Following references from Objects to Thermostat and its components is the job of Browsing.

The one part of the NodeId the client cannot know in advance is the namespace index. The server assigns it, so the program asks the connected server for its namespace table and looks the index up by URI.

The complete program

Save this program as src/main/java/org/eclipse/milo/examples/wiki/FirstClient.java.

package org.eclipse.milo.examples.wiki;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.identity.AnonymousProvider;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;

/** Reads the first-server tutorial's Temperature Variable and checks its status and Java type. */
public final class FirstClient {
  private FirstClient() {}

  /**
   * Connect to the loopback tutorial endpoint and read Temperature.
   *
   * @param endpointUrl the discovery URL of a tutorial server.
   * @return the Good Double value returned by the server.
   * @throws Exception if connecting or reading fails, the namespace is missing, or the value has an
   *     unexpected status or type.
   */
  public static double readTemperature(String endpointUrl) throws Exception {
    OpcUaClient client =
        OpcUaClient.create(
            endpointUrl,
            endpoints ->
                endpoints.stream()
                    .filter(e -> SecurityPolicy.None.getUri().equals(e.getSecurityPolicyUri()))
                    .filter(e -> e.getSecurityMode() == MessageSecurityMode.None)
                    .filter(e -> endpointUrl.equals(e.getEndpointUrl()))
                    .findFirst(),
            transport -> {},
            builder ->
                builder
                    .setApplicationUri("urn:eclipse:milo:wiki:client")
                    .setIdentityProvider(new AnonymousProvider()));
    try {
      client.connect();
      UShort namespaceIndex = client.getNamespaceTable().getIndex("urn:eclipse:milo:wiki");
      if (namespaceIndex == null) {
        throw new IllegalStateException("Server does not expose the tutorial namespace");
      }
      NodeId nodeId = new NodeId(namespaceIndex, "Temperature");
      DataValue value = client.readValue(0.0, TimestampsToReturn.Both, nodeId);
      if (value.getStatusCode() == null || !value.getStatusCode().isGood()) {
        throw new IllegalStateException("Read failed: " + value.getStatusCode());
      }
      if (!(value.getValue().getValue() instanceof Double temperature)) {
        throw new IllegalStateException("Expected a Double, received " + value.getValue());
      }
      return temperature;
    } finally {
      client.disconnect();
    }
  }

  public static void main(String[] args) throws Exception {
    String endpointUrl = args.length == 0 ? "opc.tcp://127.0.0.1:12686/wiki" : args[0];
    try {
      System.out.println("Temperature: " + readTemperature(endpointUrl));
    } finally {
      Stack.releaseSharedResources();
    }
  }
}

Run it

After compiling with the command in Getting started, run this from the application directory in a second terminal.

java -cp "target/classes:$(cat target/classpath.txt)" org.eclipse.milo.examples.wiki.FirstClient

The application prints Temperature: 21.5 and exits. Logging messages may also appear. The value is a constant that FirstServer sets, not a sensor reading. If FirstServer uses a different port, pass its complete endpoint URL as this program's first argument.

How the program works

OpcUaClient.create() first discovers the server's endpoints and passes them to the selector, which requires message security mode None, security policy None, and the exact tutorial endpoint URL. The next two callbacks configure the TCP transport and the client. This example keeps the transport defaults and explicitly selects anonymous user identity.

connect() blocks until the connection succeeds or fails. The client then resolves the namespace URI to the server's current index. An absent URI is a configuration error, so the program fails instead of substituting index 0, which is the standard OPC UA namespace and holds different Nodes. readValue() requests both timestamps and returns a DataValue. The code checks for a Good status before it inspects the Variant, then requires a Java Double for the OPC UA Double that Temperature declares.

The finally block disconnects even if connecting, the namespace lookup, the Read, or a payload check fails. The try block starts before connect() because Milo keeps trying to recover a failed connection until you disconnect. Discovery runs inside create(), before the try block, so a discovery failure leaves no client to disconnect.

The standalone entry point releases process-wide shared resources after readTemperature() returns or fails, even after a discovery failure. Release them only when no other Milo client or server in the process still uses them.

The example uses blocking calls to keep the sequence readable. In an application, keep blocking operations off event-loop threads and latency-sensitive callbacks. Connecting shows the asynchronous lifecycle.

When things go wrong

Result Cause and action
Bad_ConnectionRejected The server is stopped or the address refuses the connection. Start FirstServer and check its bind log.
Bad_TcpEndpointUrlInvalid naming /wiki/discovery The selector requires the advertised 127.0.0.1 URL exactly, so localhost does not match. create() retries discovery at a URL ending in /discovery, and that error can hide the original selection error. The server logs an ERROR for the rejected Hello. Use the URL printed by FirstServer. A different OPC UA server on the port, such as ExampleServer, can report the same status. If you tried to start FirstServer, check its output for Failed to bind endpoint, which means FirstServer did not start.
IllegalStateException: Server does not expose the tutorial namespace The endpoint serves a different model. Resolve the intended namespace URI rather than substituting index 0.
IllegalStateException: Read failed with a non-Good status The sample is unusable. An unknown NodeId returns Bad_NodeIdUnknown. An unavailable source can return Bad_OutOfService.
IllegalStateException: Expected a Double The value has a different Java type from the tutorial's OPC UA Double. Inspect the target model.

Reading covers batch results and timestamps, and Recovery and limits covers reconnects and request sizing.

Examples and reference

The broader example learning path runs larger client programs and sets up demonstration trust for secured examples. Its server binds all interfaces, so follow its network-isolation guidance.

The Wiki examples module contains the complete program.

Next steps

You can now connect to a server and read a value. Continue with the Client SDK overview. The client guide takes the same thermostat through browsing, writing, subscriptions, and Method calls. To build servers instead, start at the Server SDK overview.

Related: Getting started · Your first server · Connecting · Reading · Browsing · Writing · Subscriptions · Methods

Clone this wiki locally