Is it possible to get the Type of an Object (it's not an SObject)?

String s = 'test';

public static void doSomething(o Object) {
    // need to find out if o is a string or an integer
    type = IsThereAMethodToGetTheTypeOf(o) // ?
    if(type='String') {
        // do something for strings here ....
    } else {
        // do something else ....

I think the Type class can't help me, because it has no way to get the type of an existing object instance, right? Any there any other way?

Since there is most likely no perfect solution possible within the current platform limitations, I posted an Idea on IdeaExchange here: https://success.salesforce.com/ideaView?id=08730000000l9wHAAQ

For SObject I have already this

public static string getType(SObject obj) {
    if(obj==null) return '';
    return obj.getSObjectType().getDescribe().getName()+'';
  • One silly (but actually usable) way I found is to use a couple of try/catches to iterate over possible types by typecast and assignment like try { Integer testInt = (Integer) o; ... } catch(Exception e) { try { String testStr = (String) o; ... } } - but can we do this better? – Uwe Heim Aug 28 '14 at 11:32
  • I can't even find any sfdc doc about 'Object' class :( – Sergej Utko Aug 28 '14 at 11:48
  • 2
    There is instanceof which is better than the casting approach though that also requires hard coding of the types. And it used to return true for null but I think that may now be fixed. – Keith C Aug 28 '14 at 12:20
  • @Uwe I'm curious why you need to know the Objects type? Run-time checking of an Object's type is usually an anti-pattern in most OO languages, see explanation. – Arpi Jakab Dec 7 '16 at 23:26
  • 1
    @ArpiJakab : For simple static stuff, I agree. But if you want to write Apps capable of working on different objects and fields dynamically, the anti-pattern is your only friend left. – Uwe Heim Dec 9 '16 at 11:14

Thanks to the comment of @KeithC I was able to go on with my research and as a result, it seems not possible to get a primitive data-type dynamically as a string. There are concepts to come close to it by the usage of instance of or try/catch in brute-force-like manner.

Since the primitive types are just a few, this workaround with instanceof seems acceptable for all types excepts of collections.

For list, map and set actually I was not able to find a satisfactory solution. Brute-force is not a general option, because of infinite possibilities. String.valueOf() can give a clue but not very much: for set and map, it returns a serialization wrapped in {} and for list it's wrapped in ().

Thanks to the feedback of @sfdcfox to try instanceof List<object>, map<object, object>, and set<object>, we seem to be able to detect lists. Unfortunately it does not work for maps and sets. This are my results:

object o;
o = new map<string,object>{}; 
system.debug( o instanceof map<string,object> ); // ==> true
system.debug( o instanceof map<object,object> ); // ==> false
o = new list<string>{}; 
system.debug( o instanceof list<string> ); // ==> true
system.debug( o instanceof list<object> ); // ==> true : only here it works
o = new set<string>{}; 
system.debug( o instanceof set<string> ); // ==> true
system.debug( o instanceof set<object> ); // ==> false

Any better approaches for collections are welcome!

The documentation to this topic is rare, as @mast0r said.

This links were helpful for me coming to this conclusion:

  • Seems ripe for an idea on idea exchange Uwe! – pchittum Aug 28 '14 at 14:06
  • 1
    instanceof List<object>, map<object, object>, and set<object> should return true for the respective data types, regardless of the parameters provided. – sfdcfox Aug 28 '14 at 14:11
  • @Peter here it is: success.salesforce.com/ideaView?id=08730000000l9wHAAQ ;-) – Uwe Heim Aug 28 '14 at 16:01
  • @sfdcfox : I updated this answer above with your suggestion of instanceof List<object>, map<object, object>, and set<object>. Unfortunately it does only work for List<object> which is a really inconsistent behavior of the platform - at least from my perspective. I added my current best-possible solution (which still has flaws) here as xs.getType(Object o). Thanks to your hint, it can now at least detect Lists (as just "List", not what kind of List<Kind>). If someone figures out how to catch Map and Set or how to figure Collection<Kind>, it would be awesome! – Uwe Heim Aug 29 '14 at 7:19
  • 2
    If somebody stumbles here - I was just able to detect a Set object by using a try/catch block: try { Iterable<Object> casted = (Iterable<Object>) o; isIterable = true; } catch (Exception e) {} if (isIterable && o instanceof List<Object>) { /* list */ } else { /* set and ... ? */ }. As you notice this dumb logic leaves the door open to other Iterable implementations, but it does the job for what I need (detecting Lists and Sets instances regardless their type) – MLucci Nov 16 '15 at 21:03

The closest I could figure out with the approaches that we have right now you see below. Note that as an unfortunate we still can't detect sets and maps and we can't distinguish between decimal and double. The rest feels usable.

public class xs {
  public static string getType(Object o) {
    if(o==null) return '';              // we can't say much about null with our current techniques
    if(o instanceof SObject)            return ((SObject)o).getSObjectType().getDescribe().getName()+''; 
    if(o instanceof Boolean)            return 'Boolean';
    if(o instanceof Id)                 return 'Id';
    if(o instanceof String)             return 'String';
    if(o instanceof Blob)               return 'Blob';
    if(o instanceof Date)               return 'Date';
    if(o instanceof Datetime)           return 'Datetime';
    if(o instanceof Time)               return 'Time';
    if(o instanceof String)             return 'String';
    if(o instanceof Integer)            return 'Integer';
    if(o instanceof Long)               return 'Long';
    if(o instanceof Decimal)            return 'Decimal';  // we can't distinguish between decimal and double
    if(o instanceof Double)             return 'Double';   // we can't distinguish between decimal and double
    if(o instanceof List<object>)       return 'List';
    return 'Object';                    // actually we can't detect maps and sets and maps

Actually I'm not that kind of big test-writer, but this could be rewritten and used as test. I run it as Execute Anonymous to verify the results:

list<string>t00 = new list<string>{'test'};             system.debug('List<string> : '  + xs.getType(t00));
Account     t01 = new Account();                        system.debug('Account : '       + xs.getType(t01));
Boolean     t02 = true;                                 system.debug('Boolean : '       + xs.getType(t02));
Boolean     t03 = false;                                system.debug('Boolean : '       + xs.getType(t03));
String      t04 = 'sdfsdf';                             system.debug('String  : '       + xs.getType(t04));
Id          t05 = [select id from user limit 1][0].Id;  system.debug('Id : '            + xs.getType(t05));
Blob        t06 = Blob.valueOf('testsdf');              system.debug('Blob : '          + xs.getType(t06));
Datetime    t07 = Datetime.now();                       system.debug('Datetime : '      + xs.getType(t07));
Time        t08 = Time.newInstance(18, 30, 2, 20);      system.debug('Time : '          + xs.getType(t08));
Date        t09 = (Date) Date.today();                  system.debug('Date : '          + xs.getType(t09));
Integer     t10 = 7;                                    system.debug('Integer : '       + xs.getType(t10));
Decimal     t11 = 18.99;                                system.debug('Decimal : '       + xs.getType(t11));
Double      t12 = 77.99;                                system.debug('Double : '        + xs.getType(t12));
Long        t13 = 9;                                    system.debug('Long : '          + xs.getType(t13));
  • It's quite obvious but, if you implement something like this, note that order of the lines above is important to get proper results returned. – Uwe Heim Aug 28 '14 at 15:28
  • Also instanceof still returns true for null as of today (2014-08-28) – Uwe Heim Aug 28 '14 at 15:31
  • You can detect sets and maps using overloading. And also distinguish Double from Decimal. Added an answer as the code's too long to fit in a comment. – Dominic Aug 28 '14 at 17:55
  • To add to Uwe Heim's comment: "instanceof sObject" appears to return true for a List<sObject> -- so I believe the "if(o instanceof List<object>)" line should come first – Jelle van Geuns Dec 9 '15 at 1:07
  • Curiously, Integer is now getting picked up as a string. – Drew Kennedy Nov 9 '19 at 20:20


Here's the code I was actually looking for that does precisely what you wanted:

private string returnType( Object whatTypeAmI )
   String name = 'undefined';

   If( whatTypeAmI instanceof Integer )
       name = 'Integer';
   else if( whatTypeAmI instanceof Double)
      name = 'Double';
   else if( whatTypeAmI instanceof String)
      name = 'String';
   else if( whatTypeAmI instanceof Blob)
      name = 'Blob';
   else if( whatTypeAmI instanceof Boolean)
      name = 'Boolean';
   else if( whatTypeAmI instanceof Date)
      name = 'Date';
   else if( whatTypeAmI instanceof Datetime)
      name = 'DateTime';
   else if( whatTypeAmI instanceof Decimal)
      name = 'Decimal';
   else if( whatTypeAmI instanceof ID)
      name = 'Id';
   else if( whatTypeAmI instanceof Long)
      name = 'Long';
   else if( whatTypeAmI instanceof Time)
      name = 'Time';


Here's some old code I have I believe you could utilize for your code block to call from a class that I believe would serve the purpose of what you're looking for; particularly by using the boolean methods of the original test class. Using those, you wouldn't even need a try-catch block, just us If statements. When you finally get a "true" that's returned, you're done!

Using this method would of course require that you have data that isn't null in the list[0] or set[0] location of the collection you want to test. Potentially there could be an issue with a value of 0. Is it an integer, a decimal or a string? For maps, I'm not certain how helpful this would be, but you're a creative guy. Hopefully this will help point you towards a creative solution that will work for you.

Because I would like to directly call my Converter's class methods without creating a new instance of the class, I have defined all the methods as "static".

The methods for this class are:

  • ToString(Integer)
  • ToString(Double)
  • ToString(Long)
  • ToString(Boolean)
  • ToString(Date)
  • ToString(Date,format) sample: zConvert.ToStrong(mydate,'MM-dd-yy')
  • ToString(Time)
  • ToString(Time,format) sample: zConvert.ToStrong(myTime,'hh:mm:ss')
  • ToString(Datetime)
  • ToString(Datetime,format)
  • ToString(Decimal)
  • ToString(Decimal, ScientificNotaion) ScientificNotaion is a Boolean value and if false is passed then the string will not have scientific notations.
  • FileSizeToString(Long) Returns values such as "5.5 KB", "8 MB", etc. Parameter passed is in bytes.
  • CurrencyToString(Decimal, CurrencyChar) CurrencyChar can be "$", "£", etc

Here's the actual class:

   public class zConvert{

    // The Initial Developer of the Original Code is Sam Arjmandi. Portions created by
    // the Initial Developer are Copyright (C) 2008 the Initial Developer. All Rights Reserved.  
    // This Code is provided "As Is" without warranty of any kind.  

    public static String ToString(integer Value) {     
        /* string representation if an Integer value */     
        return Value.format(); 

    public static String ToString(Double Value) {   
        /* string representation if a Double value */
        return Value.format(); 

    public static String ToString(Boolean Value) {
        /* string representation if a Boolean value */    
        if (Value)      return 'true';
        else  return 'false'; 

    public static String ToString(Long Value) {
        /* string representation if a Long value */   
        return Value.format(); 

    public static String ToString(Date Value) {    
        /* string representation if a Date value */    
        return Value.format(); 

    public static String ToString(Date Value,String format) {
        /* string representation if a Date value with formatting */   
        Datetime temp = Datetime.newInstance(Value.year(), Value.month(), Value.day());   
        return temp.format(format); 

    public static String ToString(Datetime Value) {
        /* string representation if a Datetime value */    
        return Value.format(); 

    public static String ToString(Datetime Value,String format) {    
        /* string representation if a Datetime value with formatting */
        return Value.format(format);

    public static String ToString(Time Value) {
        /* string representation if a Time value */   
        return String.valueOf(Value); 

    public static String ToString(Time Value, String format) {
        /* string representation if a Time value with formating */
        Datetime temp = Datetime.newInstance(1970, 1, 1, Value.hour(), Value.minute(), Value.second());   
         return temp.format(format); 

    public static String ToString(Decimal Value) {
         /* string representation if a Decimal value */   
         return Value.format(); 

    public static String ToString(Decimal Value, Boolean ScientificNotation) {
          /* string representation if a Decimal value with or without Scientific Notation */   
         if (ScientificNotation)    return Value.format();
         else    return Value.toPlainString(); 

    public static String FileSizeToString(Long Value) {

       /* string representation if a file's size, such as 2 KB, 4.1 MB, etc */
       if (Value < 1024) return ToString(Value) + ' Bytes';
         else if (Value >= 1024 && Value < (1024*1024))    {
         Decimal kb = Decimal.valueOf(Value);      
         kb = kb.divide(1024,2);
         return ToString(kb) + ' KB';    
         }    else    if (Value >= (1024*1024) && Value < (1024*1024*1024))    {      
         Decimal mb = Decimal.valueOf(Value);      
         mb = mb.divide((1024*1024),2);      
         return ToString(mb) + ' MB';    
         }    else    {      
         Decimal gb = Decimal.valueOf(Value);      
         gb = gb.divide((1024*1024*1024),2);          
         return ToString(gb) + ' GB';    

    public static String CurrencyToString(Decimal Value, String CurrencyChar) {    
         return CurrencyChar + ToString(Value); 

Here's how to get 100% test coverage:

/* test string utils */
system.assertEquals('4/17/1960',zConvert.ToString(date.newInstance(1960, 4, 17)));
system.assertEquals('Apr, 26 04 11:24:40',zConvert.ToString(datetime.newInstance(2004, 4, 26, 23, 24, 40), 'MMM, dd yy hh:mm:ss'));
system.assertEquals('Apr, 17 1960',zConvert.ToString(date.newInstance(1960, 4, 17), 'MMM, dd yyyy'));
system.assertEquals('4/26/2004 11:24 PM',zConvert.ToString(datetime.newInstance(2004, 4, 26, 23, 24, 40)));
system.assertEquals('0',zConvert.ToString(decimal.valueOf('.000000000000000000000012'), true));
system.assertEquals('12.4567',zConvert.ToString(decimal.valueOf('12.4567'), false));
system.assertEquals('18:30:02.020Z',zConvert.ToString(time.newInstance(18, 30, 2, 20)));
system.assertEquals('06-30-02-302',zConvert.ToString(time.newInstance(18, 30, 2, 20), 'hh-mm-ss-ms'));
system.assertEquals('$123,456.17',zConvert.CurrencyToString(decimal.valueOf('123456.17'), '$'));
system.assertEquals('1,023 Bytes',zConvert.FileSizeToString(long.valueOf('1023')));
system.assertEquals('1,015.62 KB',zConvert.FileSizeToString(long.valueOf('1040000')));
system.assertEquals('1,020.43 MB',zConvert.FileSizeToString(long.valueOf('1070000000')));
system.assertEquals('1,015.14 GB',zConvert.FileSizeToString(long.valueOf('1090000000000')));
  • Thanks @crmprogdev, that's the way we have to go right now, I guess. Pretty much what I came up in the end. But also no full solution for set, map and list. Did you check, if your code differs between double and decimal? That I couldn't get either. I also put in the solution for SObject, so we have a single method for both use cases. – Uwe Heim Aug 28 '14 at 16:07
  • Yes there's an instance of Double in the WhatTypeAmI class. Its also accounted for in the zConvert code as well. Ditto for longs. It took me a while to find the code in my archives, thus the reason you didn't get the Edit portion first as that's what I was looking for initially. – crmprogdev Aug 28 '14 at 16:26
  • I've seen the "instanceof double" in your code, but actually I doubt that it can work. It probably returns always the result, which you try to detect first. But correct me, if I'm wrong. – Uwe Heim Aug 28 '14 at 16:31
  • I got the whatTypeAmI code from a very experienced and established developer in our SFDUG quite some time ago who was very helpful to me when I was getting started. I've not specifically tested it against a double. I can only say that I'd trust any code he's given me. I recommend you test it yourself to see if it reliably returns the results you expect. – crmprogdev Aug 28 '14 at 17:27
  • thx will do so. – Uwe Heim Aug 28 '14 at 17:29

I know this question has been answered but I have another solution that does support collections of any type.

public static String getObjectType(Object obj){

    String result = 'Date';
        Date typeCheck = (Date)obj;
    catch(System.TypeException te){

        String message = te.getMessage().substringAfter('Invalid conversion from runtime type ');
        result = message.substringBefore(' to Date');

    return result;
  • You can cast Date to Datetime, so this method yields incorrect type for any Date. In your example, it would be better to use Date instead of Datetime, since it's not possible to cast Datetime to Date and hence. – pkozuchowski Sep 13 '18 at 18:40
  • 1
    This is basically the same as the solution from @ArpiJakab posted 2 years before. – Phil W May 29 '20 at 8:12

The platform can distinguish between all these types, so if we accept that we're going to be coding a list and matching against it, we can do it this way. Let overloading sort it out instead of trying InstanceOf on an Object. This class distinguishes between Double and Decimal, and can detect any map or list I've thrown at it. I'd use statics IRL but this runs nicely in Execute Anonymous.

class TestType
    public string whatAmI(Double d)
        return 'I am a Double';

    public string whatAmI(Decimal d)
        return 'I am a Decimal';

    public string whatAmI(map<Id,list<Account>> d)
        return 'I am a map of Ids to lists of Accounts';

TestType tt = new TestType();
Decimal dec = 123.456;
Double dub = 789.987;
map<Id, list<Account>> prettySpecificMap = new map<Id, list<Account>>();

system.debug('A decimal? ' + tt.whatAmI(dec));
system.debug('A double? ' + tt.whatAmI(dub));
system.debug('Something very specific? ' + tt.whatAmI(prettySpecificMap));
  • Interesting approach! Due to the infinite possibilities it has the same flaws for maps, sets and list as the instanceof-approach - you can't write one single method in an utility class, which is reusable for each and every use case without modification. But to distinguish between Decimal and Double it would be usable in a general solution. – Uwe Heim Aug 28 '14 at 18:08
  • Thats awesome. You cant use Map<Object, Object> with this approach as a catch all? – Phil Rymek Aug 28 '14 at 18:08
  • 1
    whatAmI(map<object, object> d) doesn't seem viable. I get "method doesnt exist or incorrect signature" in what you'd think of as a catch-all case. Each specific map does have to be defined individually. Although map<Id,SObject> does work. And then you can use SObject describe to find out what it actually is. – Dominic Aug 28 '14 at 18:19
  • 1
    I tried this way but beside of the differentiation between Decimal and Double I found it less flexible. Having a general reflectional solution in mind, the biggest disadvantage for me is that you can't use it to test an instance of Object of being something more specific like String or Accounts dynamically during runtime. Say you have a mixed List<object> and want to process each element according to it's real type. So I would stick with xs.getType(Object o) defined in my answer and live with it's flaws until someone or Salesforce show us how to do it better. – Uwe Heim Aug 29 '14 at 7:07
  • 1
    This is entirely useless, and drastically inferior to the instanceof version. The decision about which version of an overloaded method to call happens at compile time, not runtime. Consequently: 1) you can't gain any information about types that isn't already known at compile time from your variable declarations or method return types, and 2) if you declare whatAmI variants for compatible types (like String and Object) you can get different results for the exact same object depending upon how you cast it. – Mark Amery Feb 28 '15 at 19:51

To read why this is typically a bad practice read here and here.

Now here is the rope to hang yourself :p

Map<List<String>, Decimal> x = new Map<List<String>, Decimal>();
Object o = x;

String theType = 'Account'; 
try {
    Account a = (Account)o; 
} catch (Exception e) {
    // MSG = 'Invalid conversion from runtime type [OBJECT TYPE] to Account'
    String m = e.getMessage();
    String[] tokens = m.split(' ');
    for (Integer i = 0; i < tokens.size(); i++) { 
        if (tokens[i] == 'type') {
            theType = tokens[i + 1];
System.debug('Type is ' + theType); // prints 'Type is Map<List<String>,Decimal>'

I make no guarantees that this code works across all Salesforce versions and it may break any time Salesforce modifies the exception error message. You've been warned!

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