Status code · Address space

BadNodeIdInvalid

BadNodeId syntax

BadNodeIdInvalid means the NodeId itself is malformed, not merely absent: a null or empty identifier, a namespace index beyond the server's NamespaceArray, or a string that does not parse as a NodeId. Compare it with BadNodeIdUnknown, which means the NodeId was valid but no such node exists.

Defined behaviour: The syntax of the node id is not valid - the server could not accept the identifier at all. (our description of the condition this code reports)

What you actually see

Every request touching one particular node fails identically, and the failing value in the log often looks visibly wrong - an empty NodeId, a stray comma, or a raw name where an identifier belongs.

Why it happens

  • A NodeId string assembled by handThe textual form is ns=<index>;<type>=<identifier> with an equals sign and a semicolon. Writing ns=2,s=Tag with a comma, omitting the ns= prefix for a non-zero namespace, or leaving whitespace around the semicolon produces something the parser rejects. Spreadsheet-driven tag imports are a rich source of these.
  • A null or default-constructed NodeIdCode that forgets to assign a NodeId sends the default value. Some servers report this as invalid, others as unknown, and a few accept it and fail later in a more confusing way. A null NodeId in a Read or a monitored item is always a client bug.
  • Namespace index outside the NamespaceArrayAsking for ns=7 on a server that publishes four namespaces is not an unknown node, it is an unusable NodeId. This is what a hardcoded index looks like after the server was reconfigured downwards, and it is the sibling failure to BadNodeIdUnknown.
  • Wrong identifier type tokenThe identifier types are numeric with i=, string with s=, GUID with g= and opaque ByteString with b=. Putting a name after i=, an unformatted value after g=, or non-base64 content after b= produces an invalid identifier even when the intended node exists.
  • A display name or browse path used where a NodeId belongsPassing what a human sees in a browse tree - a DisplayName, or a slash-separated path - into an API that expects a NodeId. The two look similar in a configuration file and are not interchangeable; browse paths are resolved with TranslateBrowsePathsToNodeIds.

How to fix it

In diagnostic order: work down, do not skip ahead.

  1. Print the NodeId exactly as your stack parsed itLog the parsed NodeId, not the source string. Seeing namespace index, identifier type and identifier separately makes almost every instance of this fault obvious in one line.
  2. Validate the namespace index against NamespaceArrayRead NamespaceArray at connect and reject any configured index that is out of range before you issue a request. Failing at configuration load with a clear message beats failing at read time on every tag.
  3. Use the SDK parser and constructorsEvery mainstream stack has a NodeId parser and typed constructors. Use them at configuration load so a malformed identifier is caught once, at startup, rather than repeatedly at runtime.
  4. Resolve names to NodeIds instead of writing themWhere the model is known, use TranslateBrowsePathsToNodeIds from a stable anchor such as the Objects folder to obtain the NodeId. This removes both syntax errors and namespace index dependence in one move.
  5. Check the source of the tag listIf the NodeIds came from an export or a spreadsheet, look for trailing whitespace, smart quotes, and locale-mangled separators. Re-export as plain text and re-import rather than fixing entries one at a time.

Where it shows up, by tool and stack

Tool / stackWhat to do there
UaExpertThe Attributes pane shows the NodeId of a browsed node in canonical form. Copying from there into configuration eliminates hand-typing errors entirely.
asyncua (Python)ua.NodeId.from_string raises on a malformed string, so validating your whole tag list at startup catches these before the first Read. client.get_node accepts both a NodeId object and a string.
node-opcuaresolveNodeId parses the textual form and throws on invalid input. Run it over the configuration at load time rather than per request.
open62541 (C)UA_NODEID_STRING and UA_NODEID_NUMERIC build identifiers safely, and UA_NodeId_parse handles the textual form. A NodeId built from a freed or reused string buffer is a common C-specific source of nonsense identifiers.
OPC PublisherPublished-nodes entries with malformed NodeIds fail per entry and are easy to miss in a large file. Validate the configuration file before deployment rather than reading the module log afterwards.

What it looks like in the log

BadNodeIdInvalid: could not parse node id string ns=2,s=Machine1.Speed
Namespace index 7 is out of range; server publishes 4 namespaces
Read request rejected: nodeId is null

Confirm the fix

Load the configuration through the SDK parser with zero errors, then read one node per namespace successfully and confirm the returned NodeId matches what you configured.

Stop it happening again

Validate every NodeId at configuration load rather than at runtime, generate tag lists from a browse or a NodeSet export instead of typing them, and store namespace URIs with identifiers so indices are resolved rather than written.

Stuck on more than one of these? The troubleshooting clinic works through broken configurations end to end, on camera.

Troubleshooting clinic →

Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.

Free download
OPC UA Status Code Diagnostic Tree
A 15-page diagnostic reference grouping every normative status code by what actually went wrong: with the first action for each, and the four questions that resolve most faults before you read a single code.
Get the free diagnostic tree →
Free, no course, no login