Skip to content

First Client

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

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

Connect to the first server, read its Temperature Variable, and disconnect. Complete Getting started and leave FirstServer running before launching this program. It uses anonymous/None on the loopback tutorial endpoint; use Client security for a secured deployment.

Complete client

Save this 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 and expected result

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. This output describes the constant provided by FirstServer, not a measurement from a physical sensor. If FirstServer uses a different port, pass its complete endpoint URL as this program's first argument.

How the read works

OpcUaClient.create() first discovers endpoints. The selector requires None mode, None policy, and the exact tutorial endpoint URL. The two configuration callbacks configure the TCP transport and client, respectively; this example keeps transport defaults and explicitly selects anonymous user identity.

connect() blocks until connection succeeds or fails. Once connected, the client resolves the namespace URI to the server's current index. An absent URI is a configuration error, not a reason to substitute index 0. readValue() requests both timestamps and returns a DataValue. The code checks that the status is Good before inspecting the Variant and requires a Java Double for the declared OPC UA Double.

The finally block disconnects even if the initial connect, namespace lookup, the Read, or payload checking fails. Discovery failures inside create() occur before this try block and produce no client to disconnect; the entry point still releases shared resources. The try begins before connect because Milo keeps trying to recover a failed connection until disconnected. The standalone entry point releases shared resources after that work. Do not call the process-wide cleanup from a component while another Milo client or server still uses them.

Failures and next operations

The common failures are:

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. Passing localhost does not match. create() retries discovery at a URL ending in /discovery; that retry error can hide the original selection error. 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; that warning means FirstServer did not start. The server logs an ERROR for the rejected Hello.
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.

The example uses blocking calls to keep the sequence readable. Keep blocking operations off event-loop threads and latency-sensitive callbacks; Connecting shows asynchronous lifecycle use. Reading covers batch results and timestamps, and Recovery and limits covers reconnect and request sizing.

Examples and reference

Use Browsing to discover more Nodes, then Writing, Subscriptions, and Methods. The broader example learning path also establishes demonstration trust for secured examples. Its server binds all interfaces, so follow its network-isolation guidance.

The source module and reproduction checks contain the complete programs, fixtures, and validation tools.

Related pages

Getting started · First server · Connecting · Reading

Clone this wiki locally