Repository navigation
Server Methods
Expose an operation with a typed signature and a result for each invocation.
Milo 1.2.0-SNAPSHOT, Java 17. API baseline 7faaf4f0516c.
Start with a registered managed namespace. Build a UaMethodNode, add it to the node manager, and connect it to its owning Object with HasComponent. CallMethodRequest identifies both the Object and Method; a discoverable Method cannot be called on an unrelated Object.
Install a MethodInvocationHandler and expose matching InputArguments/OutputArguments properties with setInputArguments(...) and setOutputArguments(...). AbstractMethodInvocationHandler checks input count, rank, dimensions, and data type before application value validation. Declaring an output property does not by itself implement an operation.
This nested handler accepts one nonnegative finite scalar Double and returns its square root. Required imports are:
org.eclipse.milo.opcua.sdk.core.ValueRanks-
org.eclipse.milo.opcua.sdk.server.methods.AbstractMethodInvocationHandlerandInvalidArgumentExceptionfrom that package org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode-
org.eclipse.milo.opcua.stack.core.NodeIdsandStatusCodes -
org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText,StatusCode, andVariant org.eclipse.milo.opcua.stack.core.types.structured.Argument
InvocationContext is the inherited nested type AbstractMethodInvocationHandler.InvocationContext. The linked runnable fixture supplies the enclosing namespace and client calls.
static final class SquareRoot extends AbstractMethodInvocationHandler {
SquareRoot(UaMethodNode node) {
super(node);
}
@Override
public Argument[] getInputArguments() {
return new Argument[] {
new Argument(
"x",
NodeIds.Double,
ValueRanks.Scalar,
null,
LocalizedText.english("Nonnegative finite input"))
};
}
@Override
public Argument[] getOutputArguments() {
return new Argument[] {
new Argument(
"root", NodeIds.Double, ValueRanks.Scalar, null, LocalizedText.english("Square root"))
};
}
@Override
protected void validateInputArgumentValues(Variant[] inputs) throws InvalidArgumentException {
Double x = (Double) inputs[0].value();
if (x == null || !Double.isFinite(x) || x < 0.0) {
throw new InvalidArgumentException(
new StatusCode[] {new StatusCode(StatusCodes.Bad_OutOfRange)});
}
}
@Override
protected Variant[] invoke(InvocationContext context, Variant[] inputs) {
return new Variant[] {new Variant(Math.sqrt((Double) inputs[0].value()))};
}
}Construct SquareRoot with the Method node, copy its two argument arrays to the node, and call setInvocationHandler(handler). A Call with 81.0 returns Good and 9.0. -1.0 returns Bad_InvalidArgument for the operation and Bad_OutOfRange for its input; a String returns an input Bad_TypeMismatch. Missing required arguments are a signature failure, not application execution.
The ordinary invoke(...) callback returns output Variants and implies Good. Override invokeResult(...) when you need a complete CallMethodResult, for example Uncertain with useful outputs. Bad results must not carry outputs, and result arrays must satisfy the service contract. AbstractMethodInvocationHandler maps malformed results to Bad_InternalError. A raw MethodInvocationHandler must enforce those invariants itself; the service replaces null results/statuses and discards malformed argument diagnostics but does not apply every invariant. getRequiredInputArgumentCount(...) supports omitted trailing inputs with the result callback; omitted arguments are absent, while an explicit null keeps its position.
InvalidArgumentException supplies one status per input and optional diagnostic information. Use it for value constraints so clients can distinguish which input failed. For service calls, the invocation context exposes DiagnosticsContext; intern diagnostic strings there and use its indexes in DiagnosticInfo. The Call service applies the client's ReturnDiagnostics mask and assembles the response StringTable. Do not assume all diagnostic strings are returned or emit secret input values.
The invocation context includes the Session, Object, and Method. The service authorizes before dispatch; directly invoking a handler is below that boundary. Check both the service status and each Call result on the client. Application exceptions, access denial, wrong Object ownership, and invalid arguments are different failures.
A shared Method can serve several Object instances. Use application-owned MethodBindings for ObjectId-specific handlers instead of repeatedly replacing its default handler. bind validates Object ownership and typed argument metadata. Each returned MethodBinding token owns one registration; closing an old token cannot remove its replacement. Condition dispatch has its own manager precedence, so use the Condition lifecycle for Condition Methods.
For a UaMethodNode subclass, ManagedAddressSpace Calls use getInvocationHandler(NodeId) and bypass an override of only getInvocationHandler(); install the handler with setInvocationHandler(...), or override the NodeId overload and apply the customization to the handler returned by super.getInvocationHandler(objectId). See method-handler migration.
Close binding tokens or remove Object registrations before deleting owners, and close the registry on namespace shutdown. Cleanup does not delete nodes, cancel in-flight invocations, or wait for callbacks already selected. Keep handler dependencies alive until those calls finish. Bound device/network work and design retries around whether the operation is idempotent: a client timeout does not prove the server skipped the command.
User guide for Milo 1.2.0-RC1 at source 69cb25087, 2026-10-12. Home · Getting started · Examples · Troubleshooting · Release notes
- Home
- Start here
- Client guide
- Server guide
- Feature guides
- Reference
- Operations
- Examples
- Release notes
- Contributing