Skip to content

Client Connecting

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

Discover a server, select an endpoint, and open a Session with OpcUaClient. Install the client artifact from Getting started and use a reachable OPC TCP discovery URL.

Milo 1.2.0-SNAPSHOT, Java 17. Source baseline 7faaf4f0516c.

Discover and select an endpoint

DiscoveryClient.getEndpoints(url) returns a future containing the advertised endpoints. An endpoint combines a URL, transport profile, message security mode, security policy, server certificate, and user-token policies. Choose all the properties your application requires. The first endpoint is not necessarily compatible or secure.

OpcUaClient.create(url, selectEndpoint, configureTransport, configureClient) performs discovery and supplies the selected endpoint to the configuration builder. Its callbacks configure the transport and client; they do not return a configuration. An empty selection produces Bad_ConfigurationError ("no endpoint selected").

On discovery or selection failure, the factory retries with /discovery appended unless the URL already has that suffix. The final exception describes that second attempt, which can instead fail with a transport or endpoint-URL status. The convenience create(url) selects a None endpoint supporting anonymous authentication. Use an explicit selector for production security requirements.

The following helper is for a local tutorial server that intentionally exposes None and anonymous access. It reads a standard namespace-zero Variable and returns a DataValue whose payload is a DateTime. It does not require application-specific NodeIds. All types and imports are available in the client artifact; uint is a static import from Unsigned.

static DataValue connectAndRead(String discoveryUrl) throws Exception {
  OpcUaClient client =
      OpcUaClient.create(
          discoveryUrl,
          endpoints ->
              endpoints.stream()
                  .filter(e -> SecurityPolicy.None.getUri().equals(e.getSecurityPolicyUri()))
                  .filter(e -> e.getSecurityMode() == MessageSecurityMode.None)
                  .findFirst(),
          transport -> {},
          config ->
              config.setIdentityProvider(new AnonymousProvider()).setRequestTimeout(uint(5_000)));
  try {
    client.connectAsync().get(10, TimeUnit.SECONDS);
    DataValue value =
        client.readValue(0.0, TimestampsToReturn.Both, NodeIds.Server_ServerStatus_CurrentTime);
    if (!value.statusCode().isGood()) throw new UaException(value.statusCode());
    return value;
  } finally {
    client.disconnectAsync().get(10, TimeUnit.SECONDS);
  }
}

Configure addresses and timeouts

Discovery can succeed while the advertised connection URL is unreachable. This is common behind a gateway, container port mapping, or server with several hostnames. Correct the server's advertisement where possible. If routing requires a client override, use EndpointUtil.updateUrl(endpoint, hostname, port) inside the selection function passed to create(...) or EndpointResolver.create(...). The resolver reruns that function on every certificate refresh. A refreshed URL that differs from the current selected URL is rejected, leaving the cached endpoint in use.

Preserve the selected policy, mode, application URI, and token policies. Keep the original discovery list for Session endpoint validation. Hostname validation uses the overridden address. Enable ValidationCheck.HOSTNAME to reject a certificate that does not cover it; DefaultClientCertificateValidator's two-argument constructor only warns about that mismatch. See client trust checks.

The transport callback controls TCP connection and handshake timeouts. It runs for the client transport and for each discovery or refresh transport. Create shared executors, event loops, and timers outside it, pass them into each builder, and close them after disconnection. Client configuration controls the request timeout, requested Session timeout, application name/URI, certificate configuration, and identity provider. The server can revise the requested Session timeout. A Java future wait deadline is an application wait budget; expiration does not prove the server cancelled a request.

Authenticate the Session

Provider Prerequisites and result
AnonymousProvider Endpoint must advertise an anonymous token; authorization still belongs to the server.
UsernameProvider(username, password) Endpoint must offer a usable username token policy. Either two-argument constructor (String password or Supplier<byte[]>) skips validation of the certificate encrypting a username token on a None endpoint. Prefer SignAndEncrypt with validation of the server certificate; if None with encrypted tokens is required, use the corresponding three-argument overload with a real validator.
X509IdentityProvider(certificate, privateKey) Endpoint must offer a compatible certificate user-token policy; the server must accept the user certificate and proof of possession.

UsernameProvider also accepts Supplier<byte[]> secrets. Milo calls the supplier for each Session activation, including reactivation after reconnect, and zeroes the returned array afterward. Return a fresh password array on each call; reusing the same array supplies a zero-filled password on the next activation.

By default it selects the first usable advertised username policy, which may resolve to None. That policy leaves the password unencrypted inside the token. On a None or Sign-only channel it crosses the wire in cleartext; SignAndEncrypt protects it with channel encryption. Select SignAndEncrypt with certificate validation, or enforce a compatible encrypted token policy and validate its encrypting certificate as described in token-policy selection.

Set the provider with setIdentityProvider. User credentials identify the Session user. They do not replace the application's certificate or its validation of the server. A username may authenticate successfully yet lack permission to read or write a node.

Connect, recover, and stop

connect() blocks and returns the connected OpcUaClient; connectAsync() returns a CompletableFuture<OpcUaClient>. Do not append .get() to connect(). The equivalent shutdown methods are disconnect() and disconnectAsync().

Connection requires a successful transport connection, SecureChannel, Session creation, and Session activation. Handle UaException from blocking methods and exceptional completion from asynchronous methods. Log the nested OPC UA status and the stage that failed; do not treat every failure as a bad password.

After connect() or connectAsync(), even a failed initial attempt leaves the client retrying connections and Sessions in the background. Call disconnect() or disconnectAsync() to stop those attempts. The helper does so in finally even when connection or reading fails.

Keep one client for ongoing work, then delete subscriptions and disconnect it when finished. Do not block transport callbacks or subscription listeners waiting for work that needs the same executors. If you supplied executors, listeners, certificate stores, or a shared reverse-connect manager, retain ownership and close those resources at application shutdown after their users stop. A disconnected client does not imply every application-owned resource has closed. Recovery responsibilities are described in Recovery and limits.

Examples and reference

Related pages

Client SDK · Client security · Recovery and limits · First client

Clone this wiki locally