OPC UA Browse Paths: Namespace Index, Not NodeId Type

Brian Holt5 min read
OPC / OPC UAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The lookup succeeds when every path segment carries the namespace index of its BrowseName. A String NodeId is not the failure: TranslateBrowsePathsToNodeIds matches qualified browse names and references, not the NodeId identifier type.

Stop changing the NodeId format

The quick fixes usually target the returned NodeId: parse the identifier as numeric, convert a String identifier, or build a different NodeId literal. None fixes this lookup because the server has not reached the point of returning the target NodeId.

Translation starts at Identifiers.RootFolder and processes each RelativePathElement. For each element, the server looks for a target whose relationship and qualified BrowseName match the request. A qualified name contains both the name text and a namespace index.

new QualifiedName(pathPart) assigns namespace index 0. That makes Objects.Server work because Objects and Server are specification-defined browse names in the standard namespace. It makes Objects.MyDemoObject fail when MyDemoObject belongs to the application namespace.

Attempt Result Reason
Change Numeric versus String NodeId handling No change NodeId identifier type is not part of browse-path matching.
Use new QualifiedName(name) for every segment Standard nodes resolve; custom nodes can fail Every segment is assigned namespace index 0.
Use one application index for the whole path The first segment can fail Objects has namespace index 0, even when its child is application-defined.
Assign the namespace for each segment The known path resolves The requested qualified browse names match the server address space.

Check the failing path element

Read the status returned for the complete translation. If the operation reports “The requested operation has no match to return,” locate the first segment that does not match rather than changing the final NodeId.

  1. Translate only the first segment, Objects, from Identifiers.RootFolder.
  2. If it resolves, use the returned Objects NodeId as the starting node and translate only MyDemoObject.
  3. If Objects fails, verify its qualified name uses namespace index 0, the reference direction is forward, and the selected reference type can reach it.
  4. If MyDemoObject fails, browse the children of Objects and read the exact BrowseName, including its namespace index. Do not substitute the display name.

The existing elements use Identifiers.HierarchicalReferences, forward direction through false, and subtype inclusion through true. Keep those settings when the target is reached through a hierarchical subtype. If manual browsing shows a different relationship, construct the path with the reference type and direction actually used by that edge.

Resolve the namespace index at runtime

Use the namespace URI as the stable identity and obtain its current index from the server namespace table. Namespace indexes are positions in that table and can change when server configuration or namespace registration order changes. Hard-code an index only in a controlled installation where that ordering cannot change.

The standard namespace URI is http://opcfoundation.org/UA/. The application namespace in this test is identified as DemoNodeManager. Confirm the configured URI in the server namespace table before using it as the lookup key.

int applicationIndex = addressSpace()
    .getNamespaceTable()
    .getIndex("DemoNodeManager");

Stop here if the lookup does not return a usable namespace index. Check the namespace table for the exact URI registered by the node manager. Changing the qualified name text cannot compensate for a missing or incorrect namespace registration.

Build each qualified name separately

For the path from RootFolder to the custom object, assign namespace index 0 to Objects and the resolved application index to MyDemoObject. Do not derive both elements from a dot-separated string unless a parallel data structure also supplies the namespace for every segment.

int applicationIndex = addressSpace()
    .getNamespaceTable()
    .getIndex("DemoNodeManager");

List<RelativePathElement> pathElements = new ArrayList<>();

pathElements.add(new RelativePathElement(
    Identifiers.HierarchicalReferences,
    false,
    true,
    new QualifiedName(0, "Objects")));

pathElements.add(new RelativePathElement(
    Identifiers.HierarchicalReferences,
    false,
    true,
    new QualifiedName(applicationIndex, "MyDemoObject")));

RelativePath relativePath = new RelativePath(
    pathElements.toArray(new RelativePathElement[pathElements.size()]));

BrowsePathResult[] pathResults = addressSpace()
    .translateBrowsePathsToNodeIds(
        Identifiers.RootFolder,
        relativePath);

The namespace belongs to the BrowseName of each segment. It is not selected from whether the destination NodeId contains a number or a string. A target may therefore resolve successfully and return a String NodeId after its qualified browse path has matched.

Browse when the path is not known

Translation is the right operation when the start NodeId, hierarchy, browse-name text, and browse-name namespace are known. It is useful for finding a known property or component below an instance without storing the final NodeId.

Browse instead when any path segment or namespace is unknown. At each level, record the returned reference type, direction, target NodeId, and qualified BrowseName. Select the child by the full qualified name, then continue from that child. This discovers the address space rather than guessing it.

Known information Use Next action
Start NodeId and every qualified path segment Browse-path translation Build one correctly namespaced element per edge.
Name text known, namespace unknown Browse Read the qualified BrowseName and namespace table.
Address-space layout unknown Browse level by level Record the actual hierarchy before creating a reusable path.
Final NodeId already known Direct NodeId operation Use the client read or write method that accepts a NodeId.

Verify the resolving branch

  1. Read the namespace table and resolve the index associated with the configured application namespace URI.
  2. Translate Objects alone with namespace index 0. Confirm that it returns a target.
  3. Translate MyDemoObject from the Objects NodeId with the application namespace index. Confirm that it returns a target.
  4. Translate the full two-element path from Identifiers.RootFolder.
  5. Compare the returned NodeId with the NodeId shown by a direct browse. A String identifier is valid and requires no conversion.
  6. Repeat after a server restart. Resolve the namespace index from the URI again rather than relying on a previously cached index.

If the single-segment tests pass but the combined path fails, compare the constructed array order, reference type, direction, and subtype flag with the two successful tests. If the custom segment still has namespace index 0, remove the string-only constructor from that segment.

FAQ

How do I find an OPC UA node from a browse path?

Start from a known NodeId, create one RelativePathElement per hierarchy edge, and assign each element the namespace index of its qualified BrowseName. Then call translateBrowsePathsToNodeIds with the starting NodeId and assembled path.

How do I resolve a custom OPC UA namespace index?

Read the server namespace table and call addressSpace().getNamespaceTable().getIndex(namespaceUri) with the exact registered URI. Resolve it at runtime because the numeric index can change.

How do I translate a path to a String NodeId?

Use the same translation used for a Numeric NodeId; identifier type needs no special handling. Correct the namespace indexes on the path elements, especially the custom child after the namespace-0 Objects segment.

Stop and contact official Unified Automation support if the namespace is registered and direct browsing shows the expected qualified name, reference, and start node, but the same values still produce no match. Provide the namespace table, each qualified browse name, starting NodeId, returned status, and the smallest failing path.

Back to blog