After an upgrade to Ignition 8.3, system.util.sendRequest() called from the Designer script console fails before the message handler ever runs. It fails whether remoteServer points at another gateway or at the local gateway's own system name. The handler RequestProgram contains nothing but return True, and the same code worked on the previous major version. The root exception is:
java.lang.ClassCastException: class java.util.Collections$UnmodifiableMap cannot be cast to class org.python.core.PyDictionary
at com.inductiveautomation.ignition.gateway.script.rpc.ScriptMessagingRpcImpl.extractPayload(ScriptMessagingRpcImpl.java:93)
at com.inductiveautomation.ignition.gateway.script.rpc.ScriptMessagingRpcImpl.dispatchRequest(ScriptMessagingRpcImpl.java:69)
This is a defect in 8.3 script messaging. Neither your payload nor your handler causes it. The workaround is to restart both the gateway and the Designer. After that, the call works consistently until someone changes the gateway's Script Message Encoding setting. A permanent fix is targeted for 8.3.1, or 8.3.2 if it misses that release.
Where along the request path does the ClassCastException fire?
Read the stack from the bottom Caused by upward and map each frame to a hop. The request starts in the Designer's Jython console. It crosses the Designer-to-gateway RPC channel, gets dispatched on the gateway, and would only then be routed to the target project's message handler, locally or across the Gateway Network.
| Hop | Frame in the trace | What happens there |
|---|---|---|
| 1. Caller (Designer) | JythonConsole$ConsoleWorker.doInBackground |
Script console executes your call |
| 2. Scripting function | SystemUtilities.sendRequest(SystemUtilities.java:827) |
Sends the request and blocks on CompletableFuture.get() for the reply |
| 3. Transport to gateway |
RouteGroupImpl.service → RpcRoutes.handle → RpcDelegate$DelegateRpcHandler.handle
|
Gateway receives the Designer RPC over its data routes |
| 4. Gateway dispatch | ScriptMessagingRpcImpl.dispatchRequest(...:69) |
Gateway starts dispatching the script message |
| 5. Payload decode | ScriptMessagingRpcImpl.extractPayload(...:93) |
Stops here. The decoded payload is a Java UnmodifiableMap, and the code casts it to a Jython PyDictionary
|
6. Routing to remoteServer
|
not reached | Gateway Network hop never happens |
| 7. Message handler | not reached |
return True never executes |
The exception is created on the gateway at hop 5. It travels back to the Designer inside the RPC response and surfaces at hop 2, which is why sendRequest throws ExecutionException wrapped around another ExecutionException.
Check: confirm your trace contains ScriptMessagingRpcImpl.extractPayload as the innermost frame. If the innermost cause is somewhere else, such as inside your handler code or a Gateway Network connection error, you have a different problem and this procedure does not apply.
Is the failure immediate, or is it a timeout?
The call sets timeoutSec = 120. A handler that never answers, or an unreachable remote gateway, makes sendRequest wait for the timeout and fail with a timeout error. This defect returns at once, because the gateway rejects the request while decoding it.
| Observation | Likely cause | Next step |
|---|---|---|
Returns in well under timeoutSec with ClassCastException ... UnmodifiableMap ... PyDictionary at extractPayload
|
8.3 script-message payload decode defect | Restart workaround (below) |
Waits the full timeoutSec, then fails |
Handler blocked, or remote gateway or Gateway Network path unavailable | Check Gateway Network connection status and handler logic |
| Error names a missing project or handler | Wrong project or messageHandler string, or handler not saved |
Compare names character by character with the gateway event script config |
| Exception raised inside handler code | Handler script error | Read the gateway log for the handler's traceback |
Check: time the call. A near-instant failure with the cast exception confirms you are dealing with the decode defect.
Can the remote gateway or the handler be ruled out?
Yes. Two facts from this installation isolate the fault to the first gateway hop:
- The handler body is
return True. Nothing in it can raise a cast exception, and the trace shows it never ran. - Pointing
remoteServerat the local gateway, usingsystem.tag.readBlocking("[System]Gateway/SystemName")[0].valueandsystem.util.getProjectName(), fails identically. With the target set to the local gateway, the Gateway Network is not involved.
Rewriting the payload on the sending side will not help. Wrapping it in dict(), converting it to a string, or building it differently changes nothing, because the cast failure happens during gateway-side decoding. A plain Python dict of {"program": ..., "machine_id": ...} is the correct payload form for sendRequest.
Check: run the local-gateway variant from the script console. If it throws the same extractPayload exception, stop troubleshooting the Gateway Network and the remote project.
What does the Designer log show at a fresh launch?
The Designer retrieves the gateway's script message encoding when it starts. If that retrieval fails, the Designer logs Failed to retrieve script message encoding, defaulting to JSON near the start of its output. Use this message to decide which failure branch you are on.
- Close every Designer session connected to the gateway.
- Launch the Designer fresh and open the project.
- Open the Designer's log output and read from the first entries. Look for
Failed to retrieve script message encoding, defaulting to JSON. - Run the
sendRequesttest from the script console.
| Log at launch | sendRequest result | Interpretation |
|---|---|---|
| Encoding warning present | Cast exception | Designer fell back to JSON while the gateway expects a different encoding. Capture the full stack trace under that warning for the vendor support case. |
| No encoding warning | Cast exception | Same defect, triggered without a visible retrieval failure. This was the case on this installation. Go to the restart procedure. |
| No encoding warning | Handler response returned | Designer and gateway agree on encoding. The defect is not active right now. |
Check: record which row you are in before restarting anything. Once you restart, that evidence is gone.
How does the Script Message Encoding setting trigger the fault?
The payload is serialized by the caller and deserialized by the gateway according to the configured Script Message Encoding in the gateway settings. The gateway's dispatch code expects the decoded payload to be a Jython PyDictionary, the same object type the handler receives as payload. When the sender and the gateway's decode path are out of step on encoding, the gateway produces a read-only Java map (Collections$UnmodifiableMap) instead. The hard cast at ScriptMessagingRpcImpl.java:93 then throws.
A restart of both the gateway and the Designer puts the two back in step. For that reason, treat the Script Message Encoding property as frozen on 8.3.0. Any change to it reopens the mismatch until both sides restart again.
Check: in the gateway settings, note the current Script Message Encoding value and whether anyone changed it since the last gateway restart. If it changed after the restart, the fault is expected to return.
How do I restore sendRequest with the restart workaround?
- Schedule a window. A gateway restart drops Perspective and Vision sessions, tag providers, and device connections for its duration.
- Leave the Script Message Encoding property alone. Do not change it before or after the restart.
- Restart the gateway that receives the Designer's RPC, which is the gateway the Designer is logged into.
- Wait until the gateway reports running and the project's gateway event scripts are loaded.
- Close all open Designer instances, then relaunch the Designer so it picks up the encoding from the freshly started gateway.
- If the target in
remoteServeris a different 8.3 gateway on which the encoding setting was changed, restart that gateway as well, so both ends of the Gateway Network hop run on their current settings. - Re-run the local-gateway test first, then the remote test.
As a diagnostic, run the same call from a gateway-scoped script such as a gateway timer or tag change script, and log the result. If the gateway-scoped call succeeds while the Designer call fails, the fault is confined to the Designer-to-gateway RPC hop, which matches the frames in the trace.
Check: the script console returns True from the local-gateway test with no exception. Only then move to the remote target.
Which Ignition release carries the permanent fix?
The root cause has been identified and a fix written. It is targeted for 8.3.1, and for 8.3.2 if it does not make 8.3.1. Before you rely on either release, read its release notes for a script-messaging or sendRequest payload fix. Then prove it on a test gateway:
- Upgrade the test gateway and launch a fresh Designer.
- Run the round-trip test and confirm it passes.
- Change the Script Message Encoding property and save, without restarting the gateway or the Designer.
- Run the test again. If it still passes, the defect is fixed in that build. If the cast exception returns, keep the restart workaround and the frozen-setting rule in place.
Check: the test in step 4 passes on the upgraded build before you upgrade production gateways that depend on sendRequest.
How do I verify the full round trip from caller to handler?
Replace the bare return True with a handler that echoes the payload. That proves the payload arrived as a readable dict and that the return path works.
# Gateway Event Scripts > Message > RequestProgram
def handleMessage(payload):
return {
"ok": True,
"program": payload["program"],
"machine_id": payload["machine_id"]
}
| Test | Pass condition |
|---|---|
Local gateway (no remoteServer) |
Returns a dict with ok = True and the same program and machine_id that were sent |
Remote gateway (remoteServer = "GatewayName") |
Same echoed dict, returned well inside timeoutSec
|
| Designer log after test | No ClassCastException and no Failed to retrieve script message encoding entry |
| Gateway log after test | No ScriptMessagingRpcImpl errors |
Run the same test from the production call site, whether that is a project script, a Perspective event, or a gateway script, with real program and machine_id values. Confirm the returned response is consumed as a dict by the calling code.
FAQ
How do I fix the UnmodifiableMap cannot be cast to PyDictionary error in Ignition 8.3 sendRequest?
Restart the gateway, then close and relaunch every Designer. The call works consistently afterward as long as the gateway's Script Message Encoding setting is not changed. The permanent fix is targeted for 8.3.1 or 8.3.2.
How do I tell whether the sendRequest failure is in my message handler or in the gateway?
Check the innermost frame of the trace. If it is ScriptMessagingRpcImpl.extractPayload, the gateway rejected the payload before routing, and your handler never ran. A handler bug shows its own traceback in the gateway log instead.
How do I test sendRequest without the Gateway Network involved?
Omit remoteServer, or set it to the local gateway's system name, and call a handler that echoes the payload. If that local call fails with the same cast exception, the fault is on the Designer-to-gateway hop rather than on the remote gateway.