Repository navigation
Client Writing
Milo 1.2.0-RC1, Java 17. Source baseline 69cb25087804.
Reading showed Setpoint at 22.0, and this page changes it. Most of the work in a write is choosing a value the server will accept and checking its answer, so the examples also send the wrong type and write the read-only Temperature. You need the connected client from Connecting and the Setpoint and Temperature NodeIds from Browsing.
A write sends a NodeId, an attribute (usually Value), and a DataValue, and the server decides whether to accept it. It checks that the node is writable, that the Session user may write it, and that the value matches the node's DataType and shape. Each write in a request gets its own StatusCode, which is the authoritative answer, so check each one even when the Write service call succeeds.
Setpoint is a scalar Double, so this method wraps the new value with Variant.ofDouble, writes it, and accepts only a Good operation status. The caller supplies the connected client and the Setpoint NodeId.
static void writeSetpoint(OpcUaClient client, NodeId setpointId, double setpoint)
throws UaException {
DataValue dataValue = DataValue.valueOnly(Variant.ofDouble(setpoint));
List<StatusCode> results = client.writeValues(List.of(setpointId), List.of(dataValue));
StatusCode status = results.get(0);
if (!status.isGood()) {
throw new UaException(status);
}
}After writeSetpoint(client, setpointId, 23.5), reading Setpoint returns 23.5. The tutorial server accepts any Double here, even one outside 0 to 100, because it has no range check. Data access adds one on the server side.
The value's type matters as much as its number. Build the Variant with Variant.ofString("23.5") instead, and the server answers Bad_TypeMismatch, writeSetpoint throws, and Setpoint keeps its previous value.
When you write to a node you discovered, read its DataType, ValueRank, ArrayDimensions, AccessLevel, and UserAccessLevel first. Access metadata is useful guidance, but permissions may change between discovery and writing, so the Write result still decides.
The Java value must match the DataType. An Int32 expects an Integer, not a Long or a numeric String. Setpoint expects a Double, so even a whole number goes in as Variant.ofDouble(23.0) rather than Variant.ofInt32(23). Use Milo unsigned types for unsigned values. Supply an array or matrix compatible with the ValueRank and dimensions. For custom values, follow the encoding and structure rules in Data types.
Use DataValue.valueOnly(...) for a normal value write, as writeSetpoint does. Supplying status or timestamps asks the server to accept those fields too. Many servers reject them, although a Milo server such as the tutorial server stores them, as Data access explains.
writeValues(nodeIds, values) takes two lists in corresponding order and returns results in that order. Milo pairs the lists by position without checking their sizes, so it drops extra entries in values without an error and throws IndexOutOfBoundsException when values is shorter. This method guards against both, and also rejects a NodeId that appears twice because it reports failures by NodeId. It then sends the batch and returns the writes that failed with their statuses:
static Map<NodeId, StatusCode> writeBatch(
OpcUaClient client, List<NodeId> nodeIds, List<DataValue> values) throws UaException {
if (nodeIds.size() != values.size()) {
throw new IllegalArgumentException("Each NodeId needs exactly one value");
}
if (new HashSet<>(nodeIds).size() != nodeIds.size()) {
throw new IllegalArgumentException("Each NodeId may appear only once");
}
List<StatusCode> results = client.writeValues(nodeIds, values);
Map<NodeId, StatusCode> failures = new LinkedHashMap<>();
for (int i = 0; i < nodeIds.size(); i++) {
StatusCode status = results.get(i);
if (!status.isGood()) {
failures.put(nodeIds.get(i), status);
}
}
return failures;
}Called with 24.0 for Setpoint and 30.0 for Temperature, it changes Setpoint to 24.0 and returns a single entry, Temperature with Bad_NotWritable. That is a partial success. A batch is not a transaction, and Milo does not roll back successful operations when another fails. Record which operations succeeded before deciding what to retry or compensate.
For more control, write(List<WriteValue>) exposes attribute IDs and optional index ranges, and returns the full WriteResponse.
The AddressSpace wrappers from Client nodes can also write, but they throw for different statuses. UaVariableNode.writeValue(...) and its attribute writers throw only for Bad and let Uncertain through, while common UaNode attribute writers and most generated model-property writers reject any non-Good result. Client nodes lists each writer's behavior, including the generated writers that still discard their status at this baseline. A local setValue(...) on a wrapper changes only its cache and sends no Write.
For asynchronous batch writes, writeValuesAsync(...) returns the status list and writeAsync(...) returns the complete response. For one Value through a wrapper, use writeAttributeAsync(AttributeId.Value, ...), which returns a future containing a StatusCode, because there is no UaVariableNode.writeValueAsync(...). Generated asynchronous property writers also expose a status. Inspect these statuses after normal completion, as you do for client.writeValues(...), because exceptional completion means a service, transport, or application error instead.
A rejected operation comes back as its own status, and the rest of the batch is unaffected. A failed request makes the blocking method throw UaException and the asynchronous method complete exceptionally.
| Status | Thermostat example and cause | What to do |
|---|---|---|
Bad_TypeMismatch |
A String or an Int32 written to Setpoint. The value's type does not match the DataType, and the previous value stays in place. | Send the Java type that matches the DataType, here Double. |
Bad_NotWritable |
Any value written to Temperature. The node's AccessLevel does not allow writing. | Treat the node as read-only. Check AccessLevel when you discover nodes. |
Bad_UserAccessDenied |
The node is writable, but the Session user may not write it. | Connect with an identity that has permission. |
Bad_NodeIdUnknown |
The server has no node with that NodeId. | Resolve the NodeId from its namespace URI, as Browsing shows. |
Bad_WriteNotSupported |
The DataValue carries status or timestamps that the Variable does not accept. | Write with DataValue.valueOnly(...). |
An index-range status such as Bad_IndexRangeInvalid
|
The server does not support or cannot apply the index range in a WriteValue. |
Check the range against the value's dimensions. |
UaException from the call |
The service, transport, or Session failed, so the outcome may be unknown. | Follow the retry guidance below. |
A timeout or broken connection can leave the outcome unknown, because the server may have acted before the response was lost. Do not automatically repeat a command-like write. Read back when that establishes the desired postcondition, keeping in mind that a read-back confirms what the server exposes, not that an external actuator physically reached a state. Otherwise use an application operation identifier or deduplication scheme when the server supports one. If you partition requests, preserve this per-operation accounting.
Writes allocate no long-lived server resource, so there is nothing to release afterward, and the caller keeps the client and controls its shutdown. When a subscription notification triggers a write, hand the write to an application executor with bounded queues and explicit error handling instead of blocking the callback.
-
WriteExample: Write scalar values and inspect their statuses.
-
ReadWriteCustomDataTypeNodeExample: Read and write application structures with typed model nodes.
You can read and write the thermostat's values on demand. To have the server report Setpoint changes as they happen, continue with Subscriptions.
Related: Client SDK · Data types · Client nodes · Recovery and limits
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