OPC UA shows a failed subscription as a ServiceFault on the Publish service, with Bad_InternalError returned for a PublishRequest. The monitored node can be valid and the client can work against other servers; the failing value is the subscription limit sent by the client. In this case, MaxNotificationsPerPublish = 4294967295 reaches a server code path that treats the unsigned value as a signed integer. Configure the field as 0, or use a value no greater than 2147483647.
What is the client actually reporting?
Start with the service and status shown in the client or server log. A node-resolution problem normally appears while browsing, reading, or creating monitored items. This failure occurs later, when the server processes a Publish request:
PublishService Returning ServiceFault for request:
[PublishRequest requestHandle=9]
StatusCode[Severity=Bad, Subcode=Bad_InternalError]
The server stack then reports:
java.lang.IllegalArgumentException: fromIndex(0) > toIndex(-1)
at java.util.ArrayList.subList(...)
at com.inductiveautomation.xopc.server.subscriptions.NotificationMessageFactory.createMessageForItems(...)
at com.inductiveautomation.xopc.server.subscriptions.BaseSubscription.returnNotifications(...)
That sequence separates the symptom from tag quality. The subscription reached the server, and the fault arose while the server assembled a notification message. Check the subscription request before changing node identifiers, namespaces, or monitored-item bindings.
| Reading | Meaning | Next check |
|---|---|---|
| Browse or read fails before subscription creation | Investigate endpoint, session, node identifier, or permissions | Validate the node independently |
Subscription creates, then Publish returns Bad_InternalError
|
Inspect Publish limits and server diagnostics | Read MaxNotificationsPerPublish
|
Server trace contains subList(0,-1)
|
A negative internal list boundary reached the message builder | Test a signed-range-safe limit |
Which subscription setting reaches the failing path?
Capture the complete CreateSubscription request rather than relying on a client library's documented defaults. The failing request used these values:
| Setting | Request value | Effect |
|---|---|---|
RequestedPublishingInterval |
period |
Requests the subscription publishing cycle |
RequestedLifetimeCount |
3000 |
Requests the lifetime counter |
RequestedMaxKeepAliveCount |
10000 |
Requests the keep-alive counter |
MaxNotificationsPerPublish |
4294967295 |
Sets the notification-count limit that triggers this failure |
PublishingEnabled |
True |
Allows Publish responses to carry notifications |
Priority |
0 |
Requests the stated subscription priority |
Change one field for the diagnostic test: MaxNotificationsPerPublish. Leave the node set, publishing interval, and other subscription inputs unchanged. A one-variable test distinguishes the notification limit from timing, node count, and data-change behavior.
Also record the revised subscription values returned by the server. Requested lifetime and keep-alive values are negotiation inputs, not proof of the active settings. If the fault remains after correcting the notification limit, compare requested and revised values and inspect the first service operation that returns a bad status.
Why does 4294967295 become a negative list boundary?
4294967295 is the maximum value representable by an unsigned 32-bit field. The maximum positive signed 32-bit value is 2147483647. If server code narrows or interprets the unsigned maximum through a signed 32-bit integer, its bit pattern represents -1.
The trace exposes the consequence. The notification builder calls a Java list operation with a start index of 0 and an end index of -1. Java rejects that range and throws IllegalArgumentException. The exception escapes the notification-building path, so Publish returns Bad_InternalError instead of a normal notification or keep-alive response.
This is why changing tags does not resolve the fault. The tag is right; the Publish-message limit is wrong for this server implementation. A client may interoperate with other OPC UA servers because they preserve the unsigned value, interpret the maximum as unlimited, revise it, or handle the limit without using a signed list boundary.
Does a smaller value isolate the root cause?
Run two controlled subscriptions against the same endpoint and nodes. First set MaxNotificationsPerPublish = 0. If Publish begins returning normal responses, the limit conversion is the resolving branch. If it still returns Bad_InternalError, capture the new server trace; a different top exception or call path identifies a separate server defect.
A second valid test is any required positive value at or below 2147483647. Keep it within the client's supported configuration range and choose it from the application's actual batching requirement. Do not use an arbitrary large number merely to imitate “unlimited.”
| Test value | Expected handling | Use |
|---|---|---|
4294967295 |
Reproduces the signed-boundary failure in this setup | Diagnostic comparison only |
0 |
Lets the server choose the notification limit | Preferred interoperability workaround |
1 through 2147483647
|
Avoids crossing the positive signed 32-bit boundary | Use when the application requires an explicit cap |
Both 0 and a positive signed-range-safe value resolved the problematic input condition. Prefer 0 when the application has no strict batching requirement because it avoids encoding a client-side maximum that the server must convert. Use an explicit positive cap when response size, processing cadence, or application batching requires deterministic limiting.
How should the Python client be corrected?
- Locate the subscription-construction call or library default that populates
MaxNotificationsPerPublish. - Set
MaxNotificationsPerPublishto0. If the API requires an explicit positive cap, set a value no greater than2147483647. - Reconnect or recreate the subscription so the server receives a new CreateSubscription request. Editing a local object without recreating the active subscription does not change the request already negotiated.
- Confirm from client tracing or server diagnostics that the new request contains the intended value.
- Create the same monitored items and resume Publish requests without changing their node identifiers.
Do not modify RequestedPublishingInterval, RequestedLifetimeCount, RequestedMaxKeepAliveCount, PublishingEnabled, or Priority during the isolation test. Multiple simultaneous edits can make a successful result impossible to attribute to the notification-limit correction.
How do you verify the workaround under operation?
- Verify that subscription creation returns normally and record the server's revised subscription values.
- Observe several Publish cycles, including a cycle with data changes and a cycle without changes. The client must continue issuing Publish requests rather than stopping after the first response.
- Confirm that monitored values update with good service-level results and that keep-alive handling does not terminate the subscription.
- Search the server log for new
Bad_InternalError,IllegalArgumentException, andfromIndex(0) > toIndex(-1)entries. - If using a positive cap, generate enough simultaneous changes to exercise notification batching and confirm that every monitored-item update is eventually delivered.
A successful single Publish response proves only that the immediate conversion did not fail. Sustained cycles and a batching test verify the complete notification path.
FAQ
What happens if MaxNotificationsPerPublish stays at 4294967295?
This server path can interpret the unsigned 32-bit maximum as -1, pass it to subList(0,-1), and return Bad_InternalError from Publish.
What happens if MaxNotificationsPerPublish is set to 0?
The server chooses the notification limit, avoiding the failing signed conversion. This is the preferred workaround when the Python application does not require a fixed per-Publish cap.
What happens if the subscription works after changing the limit?
Recreate the subscription, confirm the request carries 0 or a value no greater than 2147483647, then verify sustained Publish cycles, data changes, keep-alives, notification batching, and no new Bad_InternalError entries.