Repository navigation
Troubleshooting
Applies to Milo 1.2.0-RC1. Source baseline 69cb25087, 2026-10-12.
Use this page when a deployment check fails, or a running application reports an error, and you can name the symptom, such as a status code, an exception, a connection that never opens, or a subscription that stays quiet. The table below maps each symptom to what to check and what to do next.
A client request crosses a chain of boundaries in order, from discovery through transport, SecureChannel, and Session activation to service dispatch and finally the individual operation. A failure at one boundary stops everything after it, and the errors that follow often describe only the consequences, so find the first boundary that failed and fix that one. A successful connection shows that the client got through Session activation. Whether a Node exists, and whether the user may access it, is decided later for each operation.
Most failures reach your code as an exception whose cause chain holds a status, often inside a future wrapper. UaException.extractStatusCode(Throwable) walks the cause chain, including those wrappers, and StatusCodes.lookup(long) returns the symbolic name and description when one is known. This method combines them into a line you can log and look up in the table below.
static String describeFailure(Throwable failure) {
Optional<StatusCode> status = UaException.extractStatusCode(failure);
if (status.isEmpty()) {
return "no StatusCode in the cause chain";
}
long code = status.get().getValue();
Optional<String[]> nameAndDescription = StatusCodes.lookup(code);
if (nameAndDescription.isEmpty()) {
return String.format("unknown StatusCode 0x%08X", code);
}
String name = nameAndDescription.get()[0];
String description = nameAndDescription.get()[1];
return String.format("%s (0x%08X): %s", name, code, description);
}Log the description together with the exception, for example logger.warn("Write failed: {}", describeFailure(e), e), so the report keeps the numeric code and the full cause chain. Operation failures, such as a Bad status on one Read result, arrive as values rather than exceptions, so also check the StatusCode of each result.
| Symptom | Check | Next action |
|---|---|---|
Server startup fails with Bad_ConfigurationError ("No endpoints bound"), or WARN "Failed to bind endpoint" |
Another process listening on the port, the bind address, and which endpoints did bind | Stop the conflicting process or choose a free port, and give clients the matching URL. The tutorial FirstServer and the source examples' ExampleServer both use port 12686, so stop FirstServer before running a ClientExampleRunner example. See Your first server and Deployment. |
| Discovery times out or connection is refused | Address, TCP port, listener, DNS, firewall, discovery versus advertised URL | Test reachability from the actual client host. Compare OpcUaServer.getBoundEndpoints() with configured and advertised endpoints, and inspect OpcUaServer binding logs. Correct the advertised endpoint or the intentional client-side address mapping. If remote clients are refused while loopback clients connect, also check the connection limits, which exempt loopback. See Connecting, Server configuration, and Deployment. |
No endpoint matches, Bad_ConfigurationError ("no endpoint selected"), or Bad_TcpEndpointUrlInvalid naming /discovery
|
Discovered policies, modes, and token policies versus the client selector, plus server certificate and provider configuration | The URL factory retries discovery at <url>/discovery and reports that retry's error, which can hide the initial selection failure. Inspect DiscoveryClient.getEndpoints(url) and the selector, or use the exact advertised URL. Require the intended policy explicitly, and check OpcUaServer WARN "Omitting endpoint advertisement" messages for the reason. Do not add a silent None fallback. See the failures in Your first client and Security policies. |
| Certificate rejected | Trust and issuer chain, validity, ApplicationUri, hostname, certificate type, revocation configuration | Inspect the rejected peer certificate and validator diagnostics. Approve trust through the application's administrative process only after verifying identity. See Client security and Server security. |
Bad_SecurityChecksFailed |
Exception cause chain, client rejected-certificate store, and peer validation logs | Either side can produce this status, including a client with an empty trust list. Check the client's cause chain and quarantine first. On the server, enable DEBUG for DefaultServerCertificateValidator to see the certificate cause the returned status hides. |
Session activation fails with Bad_UserAccessDenied, Bad_IdentityTokenInvalid, Bad_IdentityTokenRejected, or Bad_SecurityPolicyRejected
|
Username and password, token shape and matching policy, user certificate acceptance, token-policy protection rules | A wrong username or password can produce Bad_UserAccessDenied or Bad_IdentityTokenInvalid, depending on the validator and token-protection path. The standard legacy encrypted username path returns Bad_IdentityTokenInvalid for rejected credentials. Invalid, unsupported, or unmatched tokens can also produce that status. A rejected X.509 user, or no compatible certificate policy, can produce Bad_IdentityTokenRejected. Incompatible protection policies can produce Bad_SecurityPolicyRejected. Fix authentication separately from application trust. See Connecting and Client security. |
Bad_NodeIdUnknown |
Namespace URI-to-index mapping, identifier kind and value, current server address space | Resolve the namespace URI after connecting, such as urn:eclipse:milo:wiki for the tutorial server, and browse the expected parent. Do not assume a namespace index from another server. |
Bad_TypeMismatch on a write |
DataType, ValueRank, ArrayDimensions, unsigned wrappers, scalar or array shape | Construct the exact Java or Milo type the Node expects, and inspect every Write result. For example, the thermostat's Setpoint takes a Double, so writing a String returns this status. See Writing and Data types. |
Bad_NotReadable or Bad_NotWritable
|
The Node's access bits | The Node disallows that operation for every user, as when you write the thermostat's read-only Temperature. Change the Node model only if the application should allow it. |
Bad_UserAccessDenied on Read, Write, Call, or Publish |
Effective user attributes, roles, AccessController, and cached read-access invalidation | Test with the intended user, and update or invalidate application permission inputs. See Access control. |
| Service call completed but values are unusable | Per-operation statuses, DataValue payload and status, source timestamps | Preserve Bad and Uncertain quality in application logic. A normal return or completed future does not mean every result is Good. See Reading. |
Bad_TooManyOperations, Bad_EncodingLimitsExceeded, Bad_RequestTooLarge, Bad_ResponseTooLarge, or Bad_TcpMessageTooLarge
|
OpcUaClient.getOperationLimits(), request count, payload sizes and encoding limits |
Partition requests and correlate results. A local override does not increase the server's limit. See Recovery and limits. |
Bad_Timeout after Write or Call |
Whether the request reached the server and whether it executed | Treat the outcome as unknown. Use application idempotency or state reconciliation before retrying side effects. |
| Recovery stalls, reconnect loops after certificate rotation, or subscriptions are not restored | Endpoint resolver, new certificate trust, keep-alive settings, and subscription status-change notifications | Config-based clients need an explicit setEndpointResolver(...). URL factories install one. Check setKeepAliveInterval(...), setKeepAliveTimeout(...), and setKeepAliveFailuresAllowed(...). A Bad_Timeout StatusChangeNotification means the server deleted that Subscription. See Connection and subscription recovery, Certificate rotation, and Timeouts. |
A quiet subscription reports no failure, so work outward from the client, starting with the Subscription and its items, then each item's own status, then the server's sampling or event routing.
Confirm that the Subscription was created, publishing is enabled, the desired items were synchronized, and each synchronization result succeeded. For item failures, inspect the MonitoredItemSynchronizationException thrown by synchronizeMonitoredItems(). Check the revised sampling and publishing intervals and queue parameters. Register listeners before operations that can produce notifications, and keep callbacks short.
For data items, inspect each item's DataValue status. A created item can publish Bad_UserAccessDenied or Bad_NotReadable, because a successful create does not prove read permission. The item recovers only when the server refreshes its access result. The default per-cycle policy checks every cycle, the cached policy needs AccessControlManager.invalidateReadAccess(...), and a custom sampler that never refreshes can keep its create-time result.
Also check data-change filters, whether the source value actually changes, the monitoring mode, and server sampling. On the tutorial thermostat, for example, an item on the constant Temperature delivers only its initial value, while an item on Setpoint reports each new value you write. See Subscriptions and Sampling.
For events, check the event item's creation status and filter result, the monitored Node's EventNotifier attribute, and the event's SourceNode with its HasNotifier or HasEventSource path. A missing SubscribeToEvents bit produces Bad_AttributeIdInvalid, and an absent event filter produces Bad_MonitoredItemFilterInvalid. On the server, register the area with ManagedNamespace.registerEventNotifier(...) and create both directions of each routing reference, because Organizes and HasComponent do not establish event routing. If events arrive on the Server object but not on an area object, inspect that hierarchy. The tutorial server fires no events until Events adds a setpoint-changed event.
After a reconnect, verify Session and subscription recovery, and detect missing data where correctness depends on continuity. A recovered connection does not recreate every application side effect or fill every notification gap.
When the table does not explain the failure, or you need help, record these facts.
- Milo artifact versions, Java version, and application revision
- endpoint URL, security policy, and mode
- identity type
- operation and Node identifiers
- full StatusCode and relevant logs
- requested and revised limits
- smallest reproducible workflow
Enable DEBUG for the failing component, such as DefaultServerCertificateValidator for server certificate causes or OpcUaServer for binding and endpoint omission. Describe which side stopped first and whether a reconnect, peer restart, or certificate rotation occurred.
Diagnostics and Wireshark explains logs, server diagnostics, and optional capture decryption. Remove credentials, private keys, and session secrets before sharing a report. Never attach key logs or decrypted captures to a public issue.
Ask usage questions in GitHub Discussions. Report reproducible bugs in GitHub Issues, with the version and evidence above. Report security vulnerabilities privately to security@eclipse-foundation.org, as described by the Eclipse security process. Do not report them in public issues or discussions.
When the status code and the table do not explain a failure, continue with Diagnostics and Wireshark. It shows which logs to enable, how to read the server's own diagnostics, and how to decrypt a capture of the failing exchange.
Related: Deployment · Client connecting · Recovery and limits · Server security · Reference
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