dotnet/aspnetcore · error · IllegalArgumentException
Expected either 'error' or 'result' to be provided, but not…
Error message
Expected either 'error' or 'result' to be provided, but not both.
What it means
Thrown by the CompletionMessage constructor when both error and result are non-null. A CompletionMessage is the wire reply to an invocation; the protocol allows either a successful result OR an error string, never both. This is an internal invariant guard - applications rarely construct CompletionMessage directly unless writing a custom hub protocol or a test server.
Solutions
- When building a CompletionMessage, choose exactly one: pass result with error==null, or error with result==null.
- Add a helper that encodes the rule: CompletionMessage.success(invId, result) vs CompletionMessage.error(invId, err).
- If the message came from the wire, validate at the parser: route to error vs result branch before constructing.
Example fix
// before new CompletionMessage(headers, invId, result, "oops"); // both set -> throws // after CompletionMessage msg = error != null ? new CompletionMessage(headers, invId, null, error) : new CompletionMessage(headers, invId, result, null);
Defensive patterns
Strategy: validation
Validate before calling
CompletionMessage success(String invId, Object result) {
return new CompletionMessage(null, invId, result, null);
}
CompletionMessage failure(String invId, String error) {
return new CompletionMessage(null, invId, null, error);
}
// always pick one branch via the helper - never pass both. Type guard
boolean isValidCompletionInputs(Object result, String error) {
return result == null || error == null; // at most one non-null
} Try / catch
try {
new CompletionMessage(headers, invId, result, error);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("either 'error' or 'result'")) {
// choose success or error branch and rebuild
} else throw e;
} Prevention
- Never set both result and error on a CompletionMessage.
- Provide factory helpers that encode the mutual-exclusion rule.
- When parsing from the wire, branch on which field is present before constructing.
When it happens
Trigger: Constructing new CompletionMessage(headers, invocationId, resultObj, errorString) with both the result and error arguments non-null. Happens in custom HubProtocol implementations, test fixtures that craft replies, or buggy server-side code that mirrors CompletionMessage.
Common situations: Test doubles for the server that fill both fields by mistake; a custom protocol adapter that copies fields without choosing one branch; upgrading the protocol and forgetting the mutual-exclusion rule.
Related errors
- Error reading JSON.
- Error reading length header.
- Error reading MessagePack data.
- Invalid invocation result kind.
- Message is incomplete.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/48636df05c0d5089.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/CompletionMessage.java:20
// The .NET Foundation licenses this file to you under the MIT license.
package com.microsoft.signalr;
import java.util.Map;
public final class CompletionMessage extends HubMessage {
private final int type = HubMessageType.COMPLETION.value;
private Map<String, String> headers;
private final String invocationId;
private final Object result;
private final String error;
public CompletionMessage(Map<String, String> headers, String invocationId, Object result, String error) {
if (headers != null && !headers.isEmpty()) {
this.headers = headers;
}
if (error != null && result != null) {
throw new IllegalArgumentException("Expected either 'error' or 'result' to be provided, but not both.");
}
this.invocationId = invocationId;
this.result = result;
this.error = error;
}
public Map<String, String> getHeaders() {
return headers;
}
public Object getResult() {
return result;
}
public String getError() {
return error;
}
View on GitHub (pinned to 3600ca084e)