Repository navigation
Client Reading
Milo 1.2.0-RC1, Java 17. Source baseline 69cb25087804.
With the thermostat's NodeIds from Browsing and the connected client from Connecting, you can read its values. This page reads Temperature on its own, then Temperature and Setpoint in one request, and shows how to interpret the status, timestamps, and Java value in each result.
Every read result is a DataValue. It holds the value, a status code that says whether the value is usable, and timestamps that say when it was produced. The status belongs to one operation. A Read service call can succeed as a whole while one node's result is Bad, so you check each result's status before you use its value. Then you check the value's Java type, because a Variant holds whatever type the server sent.
client.readValue(maxAge, timestamps, nodeId) reads one node's Value attribute and returns its DataValue. This method reads Temperature, rejects a non-Good status before it touches the payload, and then requires a Java Double. temperatureId is the NodeId you found by browsing, or new NodeId(namespaceIndex, "Temperature") with the index from tutorialNamespaceIndex. The method uses the shared client without connecting or disconnecting it.
static double readTemperature(OpcUaClient client, NodeId temperatureId) throws UaException {
DataValue value = client.readValue(0.0, TimestampsToReturn.Both, temperatureId);
if (!value.statusCode().isGood()) {
throw new UaException(value.statusCode());
}
Object body = value.value().value();
if (!(body instanceof Double temperature)) {
throw new IllegalStateException("Expected a Double, got " + body);
}
return temperature;
}Against the tutorial server it returns 21.5, the constant FirstServer gives Temperature. Its status check rejects Bad and Uncertain results alike.
For standard namespace-zero nodes, use the NodeIds constants. For application nodes, resolve the namespace URI with the connected client's namespace table, as tutorialNamespaceIndex does, instead of persisting another server's numeric namespace index. To read an attribute other than Value, build a ReadValueId and use client.read(...).
DataValue.value() is a Variant, and the Variant's value() is its Java payload, which can be null. The node's DataType decides the Java type. A Double such as Temperature arrives as Double, an Int32 as Integer, a UInt32 as UInteger, and an OPC UA DateTime as DateTime.
An array or structure is not automatically a scalar Java value, so check the server's DataType and ValueRank before casting. Structured values commonly arrive as ExtensionObject, which you decode with the appropriate context as described in Data types.
sourceTime() is the source's value timestamp, and serverTime() is the server's timestamp. Either may be absent or null-valued. Request TimestampsToReturn.Both, Source, Server, or Neither as needed. Source timestamps apply to the Value attribute, not arbitrary node metadata. If you substitute receipt time, record that change in meaning, and when you hand a reading to storage or a UI, keep its status and timestamps with it.
maxAge is in milliseconds. Zero requests a fresh read under the server's semantics, but it does not guarantee a physical device acquisition for every call. Device caching, sampling, and unavailable sources remain server responsibilities.
readValues(maxAge, timestamps, nodeIds) reads several nodes in one request and returns the results in request order. This method reads both thermostat values and prints each one with its own status:
static void printThermostat(OpcUaClient client, NodeId temperatureId, NodeId setpointId)
throws UaException {
List<NodeId> nodeIds = List.of(temperatureId, setpointId);
List<DataValue> values = client.readValues(0.0, TimestampsToReturn.Both, nodeIds);
for (int i = 0; i < nodeIds.size(); i++) {
NodeId nodeId = nodeIds.get(i);
DataValue value = values.get(i);
if (value.statusCode().isGood()) {
System.out.println(nodeId.getIdentifier() + " = " + value.value().value());
} else {
System.out.println(nodeId.getIdentifier() + " failed: " + value.statusCode());
}
}
}Against a freshly started tutorial server it prints:
Temperature = 21.5
Setpoint = 22.0
The loop pairs each result with its input NodeId by position. Milo returns the server's result list without checking its length, so production code should also verify that it holds one result per NodeId. Each status stands on its own. Pass an unknown NodeId in place of setpointId, and that line reports Bad_NodeIdUnknown while Temperature still reads 21.5.
readValuesAsync(...) returns a future with the same per-item results. readAsync(...) exposes the ReadResponse and its per-operation DataValues. Service faults and transport or Session failures complete the future exceptionally, while ordinary Bad node results arrive in a normally completed future. Attach error handling to the entire chain, including application decoding, and do not block callback threads waiting for a read.
High-level methods set ReturnDiagnostics=0, so they do not request DiagnosticInfos. To request them, build a ReadRequest with a RequestHeader diagnostics mask and use sendRequest(...) or sendRequestAsync(...). The server may still omit them.
A batch is not a transaction or a guarantee that all source values were sampled together. The service methods send the requested list as supplied, so partition large reads using Recovery and limits. MaxNodesPerRead is only one bound, and large arrays can hit message-size limits before node-count limits. Retrying a whole read batch is generally less consequential than retrying writes, but decide whether historical consistency or cost makes a retry inappropriate.
A failed operation comes back as a status in its own DataValue, while the rest of the batch can still succeed. A failed request makes the blocking method throw UaException and the asynchronous method complete exceptionally.
| What you see | Meaning | What to do |
|---|---|---|
Bad_NodeIdUnknown in one DataValue |
The server has no node with that NodeId. | Check the namespace index and identifier, for example after a server reconfiguration. |
Bad_NotReadable or Bad_UserAccessDenied in one DataValue |
The node's AccessLevel does not allow reading, or the Session user may not read it. | Read a different node, or connect with an identity that has permission. |
Bad_AttributeIdInvalid in one DataValue |
The node does not have the requested attribute. | Check the attribute you passed to client.read(...). |
Bad_OutOfService or another Bad status from the source |
The server cannot get a value from the underlying source. | Treat the value as unusable and keep the status with the reading. |
| An Uncertain status | The server returned a value but doubts its quality. | Accept it only through an explicit application policy, and keep the original status with the value. |
UaException from the call |
The service, transport, or Session failed, and no per-node results exist. | Retry the batch if your application allows it, as discussed above. |
| A cast or type-check failure in your code | The application and the server's model disagree. It is not a network failure. | Compare the application with the server's DataType and ValueRank. |
Reads create no subscription and reserve no cursor, so there is nothing to release afterward. The caller still owns the client connection. Use a bounded wait in top-level workflows.
-
ReadExample: Read service values from the local ExampleServer.
-
ReadNodeExample: Read typed model wrappers and cached attributes.
You can read Temperature and Setpoint and tell a usable value from a failed one. To change Setpoint and handle the server's answer, continue with Writing.
Related: Client SDK · Client nodes · Data types · 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