[SalesForce] Why is Map> an unsupported parameter type in future methods

I am trying to create some junction records via callout (in this case, SkillUser records). Since I am doing so from a trigger, I need to use a @future annotated method. The Apex Developer Guide states the following about Future Methods (emphasis mine):

Methods with the future annotation must be static methods, and can only return a void type. The specified parameters must be primitive data types, arrays of primitive data types, or collections of primitive data types. Methods with the future annotation cannot take sObjects or objects as arguments.

It seems to me that Map<Id, Set<Id>> is a collection of primitive data types. However, the compiler states this parameter type is unsupported, despite the fact some other Map types are supported. Why is that?

Compiles

@future
public static void doStuff(Map<Id, Id> input) { }
@future
public static void doStuff(Set<Id> input) { }
@future
public static void doStuff(List<Id> input) { }

Compile Fail

@future
public static void doStuff(Map<Id, Set<Id>> input) { }

Unsupported parameter type Map<Id,Set<Id>>

I know I can use workarounds like JSON serialization, but I would like to understand why this type does not count as a collection of primitive data types.

Best Answer

The list of primitives doesn't include Map, Set, or List. Therefore, what you have is a collection of non-primitives. They've also explicitly excluded Object, so you can't weasel your way around that limitation that way, either. You cannot include any nested collections at all, including things like List<List<String>> or Set<List<String>>, or any other combination of nested collections.

This restriction has to do with how future methods serialize their parameters; it literally cannot support complex data types. It was originally placed on future methods to limit the complexity of serialization, which is why salesforce allowed 50 future calls as opposed to five Batchable calls per transaction.

Since then, we've been given Queueable, which suggests that future should enjoy the same serializing features, but it hasn't been upgraded, and probably won't be for the foreseeable future, because we have a newer feature that gives this this ability already. If you need future-like behavior with complex types, use Queueable instead.