---
title: "Invalid conversion from runtime type"
url: "https://trailassembly.com/invalid-conversion-from-runtime-type/"
topic: "Apex and Development"
published: "2026-09-11T09:58:18+00:00"
updated: "2026-10-06T13:52:55+00:00"
error_codes: "JSONException, TypeException"
---

# Invalid conversion from runtime type

**Briefly:** you cast an `Object` to a type it is not. Apex checks casts at runtime, not compile time, so the code compiles fine and fails the first time the value is the wrong shape. The message names both types, which is the whole diagnosis.

```apex
Object o = 'hello';
Integer i = (Integer) o;
// System.TypeException: Invalid conversion from runtime type String to Integer
```

## The four situations that produce it

**Deserialised JSON.** `JSON.deserializeUntyped` returns `Object`, and every value inside is an `Object`. A number in JSON arrives as `Integer`, `Decimal` or `Double` depending on how it was written, so casting to `Integer` works until someone sends `5.0`.

**Flow variables into invocable Apex.** A flow passes values as generic types. An invocable method declaring `List<String>` and receiving IDs works. One declaring `Integer` and receiving a currency field does not.

**A map of Object.** `Map<String, Object>` is convenient and gives up all type safety. Every read out of it is a cast, and every cast is a potential runtime failure.

**sObject downcasting.** Casting a generic `SObject` to a specific type is fine when the record is that type and throws when a list contains a mixture, which happens more often than you would expect after a SOSL search. Casting a list element that turned out to be absent throws [a different exception](https://trailassembly.com/list-index-out-of-bounds/) entirely.

## The numeric case, in detail

This is the one that reaches production. `JSON.deserializeUntyped('{"n": 5}')` gives you an `Integer`. The same call with `{"n": 5.0}` gives you a `Decimal`. Casting blindly to either one fails half the time, and which half depends on the system sending the payload.

```apex
Object raw = ((Map<String, Object>) parsed).get('amount');
Decimal amount = raw == null ? null
    : raw instanceof Decimal ? (Decimal) raw
    : Decimal.valueOf(String.valueOf(raw));
```

Going through `String.valueOf` and back is deliberately blunt and it is reliable. The tidier answer is not to use `deserializeUntyped` at all.

## The fix that removes the whole class of bug

Define an Apex class matching the payload and deserialise into it. The platform does the conversion, and a mismatch gives you a `JSONException` naming the field rather than a `TypeException` naming two types with no context.

```apex
public class OrderPayload {
    public String externalId;
    public Decimal amount;
    public List<LineItem> lines;
    public class LineItem { public String sku; public Integer qty; }
}

OrderPayload p = (OrderPayload) JSON.deserialize(body, OrderPayload.class);
```

You get compile-time types everywhere downstream, and the error, when it comes, says `Cannot deserialize instance of int from VALUE_STRING value notanumber ... at [line:1, column:12]`, with the position in the payload. The full set of those messages is in [JSON deserialization errors in Apex](https://trailassembly.com/apex-json-deserialization-errors/). That is a debuggable message.

Verified in a Summer ’26 scratch org on 14 September 2026: `Integer.valueOf(9.87)` returns **9** while `Math.round(9.87)` returns **10**; `String.valueOf(null)` returns the four-character string `null`, not a null String. And `instanceof` against a null `Object` returns **false**. Note that testing `instanceof` on a variable already typed as the target class is a compile error, not a runtime false.

## Use `instanceof` before casting, not after catching

Catching `TypeException` and carrying on is a smell. If a value can legitimately be two types, check:

```apex
if (record instanceof Account) {
    Account a = (Account) record;
}
```

`instanceof` on null returns false, which is convenient and occasionally surprising: a null value skips every branch silently rather than throwing.

## Frequently asked

**Why does it compile?** Because a cast from `Object` is a promise you make to the compiler. It believes you and checks at runtime.

**Is `String.valueOf` safe on any Object?** Yes, including null, which returns the string “null” rather than a null String. That has caught people.

**What about `Integer.valueOf` on a Decimal?** It truncates rather than rounding, and it throws on a value beyond the Integer range. `Math.round` is usually what you meant.

---

Source: https://trailassembly.com/invalid-conversion-from-runtime-type/
Trail Assembly — independent Salesforce write-ups.
