5

One of my fellow developers was working on a class that converted feedback from a REST callout into SObjects. Part of the class was storing the feedback in a map, and then attempted to upsert the map values. The code looked similar to the snippet below:

Map<Id, Feedback__c> feedbackMap = new Map<Id, Feedback__c>();

// Fill map and perform other functions....

List<Feedback__c> updatedFeedback = (List<Feedback__c>) feedbackMap.values();
upsert updatedFeedback;

The upsert resulted in the below error:

System.TypeException: DML on generic List only allowed for insert, update or delete

I'd remembered getting this before when attempting to perform DML on something that wasn't an SObject or List<SObject>, like Set<SObject>, so I recommended he try adding the map values to a list instead of just casting the map values:

List<Feedback__c> updatedFeedback = new List<Feedback__c>();
updatedFeedback.addAll(feedbackMap.values());
upsert updatedFeedback;

This worked no problem. I'm glad that my solution worked, but I want to know exactly why it worked. Was there something that was causing the cast map values to still be typed as List<Object>? Why did adding the values work instead of casting?

  • 2
    Are you sure that the map being used in the code that was failing was declared with a concrete SObject type instead of being a Map<Id, SObject>? – Derek F Apr 9 at 20:09
  • So it was declared as Map<Id, SObject>. Map.values() should still return as least as List<SObject>, which should upsert fine, right? And why did explicitly casting it as a list of a concrete SObject not work as we expected? – Ryan Dinesman Apr 9 at 20:48
  • The error you got rules out upsert on List<SObject>, but I'm not sure why casting to a collection of a concrete SObject type first didn't work. I may play around with that a bit tomorrow. – Derek F Apr 9 at 21:44
3

I coudn't replicate the issue from provided example. If you could elaborate I may be able to give you more precise answer, why addAll() worked for you.


The key here is that the declarion has been with Map<Id, SObject>. And upsert does not work on SObject collections (there is a ways arround it).

I was able to reproduce the same behaviour with following code. (I belive you could have something similar at the beginning)

String response = '[{"attributes": {"type": "Account"}, "id": "00100000000000001", "name":"Test"}]';
List<SObject> objs = (List<SObject>)JSON.deserialize(response, List<SObject>.class);

Map<Id, SObject> sObjectMap = new Map<Id, SObject>(objs);
List<Account> acountList = (List<Account>) sObjectMap.values();

upsert acountList;
// Runtime exception: System.TypeException: DML on generic List<SObject> only allowed for insert, update or delete

Simplified example, everyone can try:

List<Account> accounts = (List<Account>) new List<SObject>{
    new Account(Name = 'Doe'),
    new Account(Name = 'Jackson')
};
  • I've created collection variable of type List that should hold Account objects.
  • I've actually initialized it as List<SObject>.
  • But my List<SObject> holds Account instances only.
  • Apex will let me deploy (and build) the class.
  • Remember accountList variable actually points to List<SObject>.

So far so good, you could be doing insert, update and delete DML statements on accountList.

And if I try upsert

upsert accountList;
// Runtime exception: System.TypeException: DML on generic List<SObject> only allowed for insert, update or delete

Why? Becuase casting List collections actually does nothing. Runtime initialized accountList as List<SObject> and does not care that variable pointing to the object says it should be List<Account>.


Now I will try to explain the confusion behind this.

  • Casting isn't magic, you are only telling the compiler that Object A is more of a type Object B (reference).

  • Apex is based on JVM. In Java you can't cast collections (without generics you can't and we don't have generics in Apex).

  • Not related: In Java you can cast arrays. I don't how Apex List is implemented, but I believe it's rather the ArrayList than simple old Array

  • What is very confusing is that Apex compiler will let you cast List collections but no Map and Set.


List<Account> accounts = (List<Account>) new List<SObject>();
// Will compile, no runtime exceptions
Map<Id, Account> accountMap = (Map<Id, Account>) new Map<Id, SObject>();
// Will compile, but runtime exception:
// FATAL_ERROR System.TypeException: Invalid conversion from runtime type Map to Map
Set<Account> accountSet = (Set<Account>) new Set<SObject>();
// Won't compile with message:
// Incompatible types since an instance of Set<SObject> is never an instance of Set<Account>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.