JamesNK/Newtonsoft.Json · error · JsonException
Serialization Callback '{1}' in type '{0}' must return void.
Error message
Serialization Callback '{1}' in type '{0}' must return void. What it means
Serialization callback methods must return void. IsValidCallback checks method.ReturnType != typeof(void) and throws a JsonException (line 1332). The serializer ignores any return value, so a non-void return indicates a misdeclared hook.
Source
Thrown at Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs:1332
if (currentCallback != null)
{
throw new JsonException("Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '{3}'.".FormatWith(CultureInfo.InvariantCulture, method, currentCallback, GetClrTypeFullName(method.DeclaringType!), attributeType));
}
if (prevAttributeType != null)
{
throw new JsonException("Invalid Callback. Method '{3}' in type '{2}' has both '{0}' and '{1}'.".FormatWith(CultureInfo.InvariantCulture, prevAttributeType, attributeType, GetClrTypeFullName(method.DeclaringType!), method));
}
if (method.IsVirtual)
{
throw new JsonException("Virtual Method '{0}' of type '{1}' cannot be marked with '{2}' attribute.".FormatWith(CultureInfo.InvariantCulture, method, GetClrTypeFullName(method.DeclaringType!), attributeType));
}
if (method.ReturnType != typeof(void))
{
throw new JsonException("Serialization Callback '{1}' in type '{0}' must return void.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method));
}
if (attributeType == typeof(OnErrorAttribute))
{
if (parameters == null || parameters.Length != 2 || parameters[0].ParameterType != typeof(StreamingContext) || parameters[1].ParameterType != typeof(ErrorContext))
{
throw new JsonException("Serialization Error Callback '{1}' in type '{0}' must have two parameters of type '{2}' and '{3}'.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method, typeof(StreamingContext), typeof(ErrorContext)));
}
}
else
{
if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != typeof(StreamingContext))
{
throw new JsonException("Serialization Callback '{1}' in type '{0}' must have a single parameter of type '{2}'.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method, typeof(StreamingContext)));
}
}
prevAttributeType = attributeType;View on GitHub (pinned to 4f73e74372)
Solutions
- Change the method's return type to void.
- If using async work, perform synchronous work in the callback or move async logic elsewhere (do not return Task from a callback).
Example fix
// before
public class Model {
[OnDeserialized]
internal bool OnDeserialized(StreamingContext c) => true; // throws
}
// after
public class Model {
[OnDeserialized]
internal void OnDeserialized(StreamingContext c) {}
} Defensive patterns
Strategy: validation
Validate before calling
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
foreach (var m in typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (attrs.Any(a => m.IsDefined(a, false)) && m.ReturnType != typeof(void))
throw new InvalidOperationException($"Callback method {m.Name} must return void.");
} Type guard
static bool AllCallbacksReturnVoid(Type t)
{
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
return t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => attrs.Any(a => m.IsDefined(a, false)))
.All(m => m.ReturnType == typeof(void));
} Try / catch
try { JsonConvert.DeserializeObject<T>(json); }
catch (JsonException ex) when (ex.Message.Contains("must return void"))
{
// change the callback return type to void
} Prevention
- Declare callback methods with a void return type.
- Do not use async Task for callbacks; do synchronous work only.
When it happens
Trigger: An On* callback method declared with a return type (int, bool, Task, the model type, etc.) instead of void.
Common situations: Reusing an existing helper that returns a value as a callback, or declaring the callback as async Task (async void or Task both break the void requirement).
Related errors
- Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '
- Invalid Callback. Method '{3}' in type '{2}' has both '{0}'
- Virtual Method '{0}' of type '{1}' cannot be marked with '{2
- Serialization Error Callback '{1}' in type '{0}' must have t
- Serialization Callback '{1}' in type '{0}' must have a singl
AI-assisted analysis of JamesNK/Newtonsoft.Json@4f73e74372 (2026-08-07).
Data as JSON: /api/errors/321e649bb7b4b0ba.
Report an issue: GitHub.