Skip to content

Client Methods

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

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

A write stores any Double you send to Setpoint. A Method lets the server apply its own logic instead. The thermostat's AdjustSetpoint method, for example, adds a delta to the setpoint, refuses any result outside 0 to 100, and returns the new value. You need a connected and authorized Session from Connecting. If the Setpoint subscription from Subscriptions is still running, it prints the adjusted setpoint.

How a method call works

Every call names two nodes, and a CallMethodRequest carries both with the input arguments. The ObjectId identifies the Object on whose behalf the method runs, and the MethodId identifies the callable Method. For AdjustSetpoint they are Thermostat and AdjustSetpoint, which you find by browsing Thermostat's HasComponent references as in Browsing. The Method's InputArguments and OutputArguments properties describe its signature. AdjustSetpoint takes one Double input named Delta and returns one Double output named Setpoint.

Call the Method on the Object that owns it, not on the Method's type-definition object. A server can share one Method node among several Objects and dispatch to a different handler for each, so the same MethodId does not imply the same object binding everywhere.

The Method's Executable and UserExecutable attributes describe whether you can call it, but the Call result is authoritative, because server state or permissions can change after you read them.

Adjust the setpoint

This method calls AdjustSetpoint with a delta and returns the new setpoint. Build the two NodeIds from the tutorial namespace index as Your first client does for Temperature: new NodeId(namespaceIndex, "Thermostat") and new NodeId(namespaceIndex, "AdjustSetpoint").

static double adjustSetpoint(
    OpcUaClient client, NodeId thermostatId, NodeId adjustSetpointId, double delta)
    throws UaException {
  Variant[] inputs = {new Variant(delta)};
  var request = new CallMethodRequest(thermostatId, adjustSetpointId, inputs);

  CallResponse response = client.call(List.of(request));
  CallMethodResult result = requireNonNull(response.getResults())[0];
  if (!result.getStatusCode().isGood()) {
    throw new UaException(result.getStatusCode());
  }

  Variant[] outputs = requireNonNull(result.getOutputArguments());
  Object newSetpoint = outputs[0].value();
  if (!(newSetpoint instanceof Double setpoint)) {
    throw new IllegalStateException("Expected a Double, got " + newSetpoint);
  }
  return setpoint;
}

client.call(...) returns the whole CallResponse. The method checks the CallMethodResult status before it touches the outputs, because outputs mean something only when the method succeeded, and turns a non-Good status into a UaException. It then checks the output's Java type against the signature.

On a freshly started tutorial server, adjustSetpoint(client, thermostatId, adjustSetpointId, 1.5) returns 23.5, and Setpoint then reads 23.5 too. A delta that would take the setpoint below 0 or above 100 throws a UaException with Bad_OutOfRange and leaves Setpoint unchanged. When things go wrong lists every outcome.

The caller owns the connection and any asynchronous work around the call. To call AdjustSetpoint in response to a subscription notification, hand the call to a bounded application executor rather than making it inside the listener, where a blocking call stalls notification delivery. Keep the result and its error cause so you can report them.

Call through a UaMethod

When you work with node wrappers (Client nodes), UaObjectNode can find a Method by its BrowseName. getMethod("AdjustSetpoint") browses Thermostat for a Method component and returns a UaMethod that holds both NodeIds and the argument metadata.

static double adjustSetpointWithWrapper(OpcUaClient client, NodeId thermostatId, double delta)
    throws UaException {
  UaObjectNode thermostat = client.getAddressSpace().getObjectNode(thermostatId);
  UaMethod adjustSetpoint = thermostat.getMethod("AdjustSetpoint");

  Variant[] inputs = {new Variant(delta)};
  Variant[] outputs = adjustSetpoint.call(inputs);

  Object newSetpoint = outputs[0].value();
  if (!(newSetpoint instanceof Double setpoint)) {
    throw new IllegalStateException("Expected a Double, got " + newSetpoint);
  }
  return setpoint;
}

getMethod(String) assumes the Method's BrowseName is in the Object's namespace, which holds here because both nodes live in the tutorial namespace. Use getMethod(QualifiedName) when the Method belongs to a different namespace, such as an application Method under the namespace-zero ObjectsFolder. Either form throws a UaException with Bad_NotFound when no Method component has that BrowseName. UaObjectNode.callMethod(name, inputs) combines the lookup and the call.

UaMethod.call(Variant[]) returns the output Variants on success and throws UaMethodException for a non-Good operation result. The exception keeps the input-argument results and any diagnostics the server supplied, although UaMethod requests none. callAsync(...) completes exceptionally for both service and operation failures. Method wrappers hold client and model references and need no separate shutdown.

Build inputs from the signature

getInputArguments() and getOutputArguments() on a UaMethod return each argument's name, DataType, ValueRank, array dimensions, and description. An empty array can also mean the argument property could not be read, so it does not prove the Method takes no arguments.

Build inputs in the declared order with the matching Milo Java representation, which is a Double for AdjustSetpoint. A missing argument, an extra argument, the wrong scalar or array rank, or an invalid value can each fail differently. Correct types do not make a value valid either. AdjustSetpoint accepts any Double by type but still rejects NaN, infinity, and results outside 0 to 100, just as another server might reject a negative speed or an illegal state transition. For structures and ExtensionObjects, see Data types.

Call several methods and request diagnostics

client.call(List<CallMethodRequest>) takes several requests and returns one CallResponse. Check the service result, the expected number of results, and each CallMethodResult separately, because a Good service result only means the server processed the Call service request. getInputArgumentResults() explains the status of each argument. Decode outputs only when the method result permits it, and check them against the signature. A batch of method calls is not atomic.

The high-level calls set ReturnDiagnostics=0 and so do not request getInputArgumentDiagnosticInfos(). To request diagnostics, build a CallRequest with your own RequestHeader and send it through sendRequest(...) or sendRequestAsync(...). Servers may still omit them. A null or empty getInputArgumentResults() or getInputArgumentDiagnosticInfos() array does not mean the arguments passed validation.

When things go wrong

AdjustSetpoint reports each failure in the method result, and when an argument check fails, the input-argument result for Delta says why. Setpoint is unchanged in every failure case.

Cause Result
Setpoint plus Delta falls outside 0.0 to 100.0 Bad_OutOfRange
Delta is NaN, infinite, or a null Variant Bad_InvalidArgument, with Bad_OutOfRange as the input-argument result for Delta
Delta is a String or another non-Double value, rejected by the SDK's argument checks Bad_InvalidArgument, with Bad_TypeMismatch as the input-argument result for Delta

adjustSetpoint turns each of these into a UaException with the method result. When you need the per-argument status for a UI or a log, read getInputArgumentResults() from the raw CallMethodResult, or catch UaMethodException and call its getInputArgumentResults() when you use UaMethod.

A timeout or exceptional completion leaves the outcome unknown, because a method may perform its side effects before the connection breaks. AdjustSetpoint is relative, so repeating a call that actually succeeded applies the delta twice. Retry only when the operation is idempotent or the application protocol supplies a transaction identifier or another way to determine the outcome. For AdjustSetpoint, reading Setpoint can tell you whether the call took effect, as long as nothing else changed it in the meantime.

Examples and reference

Next steps

You have changed the setpoint through raw NodeIds and through a UaMethod. To learn how node wrappers such as UaObjectNode cache attributes and when they go to the server, continue with Client nodes.

Related: Client SDK · Server methods · Data types

Clone this wiki locally