---
title: "JSON deserialization errors in Apex"
url: "https://trailassembly.com/apex-json-deserialization-errors/"
topic: "Apex and Development"
published: "2026-09-11T10:36:47+00:00"
updated: "2026-10-06T13:52:57+00:00"
error_codes: "JSONException"
---

# JSON deserialization errors in Apex

**The quick version:** the JSON does not match the shape you told Apex to expect. The message says which field and, usefully, the character position. Most of these come from one of three things: a number arriving as a string, a field name that does not match your property, or a reserved word you cannot use as a property name.

Two real messages from a scratch org:

```apex
JSON.deserialize('{"Name":"x","NumberOfEmployees":"notanumber"}', Account.class);
// System.JSONException: Cannot deserialize instance of int from VALUE_STRING
// value notanumber or request may be missing a required field at [line:1, column:12]

JSON.deserializeStrict('{"a":1,"unknown":2}', Simple.class);
// System.JSONException: Unknown field: Simple.unknown
```

## strict or not, and why it matters

`JSON.deserialize` ignores fields it does not recognise. `JSON.deserializeStrict` throws on them. Neither is correct in general:

- Use **strict** for a payload you control, or when a new field appearing should be treated as a change that needs handling. It catches typos in your own class immediately.

- Use **lenient** for a third-party API you do not control, because the vendor will add fields without telling you and you do not want a release note to break your integration.

Picking lenient for convenience and then wondering why a value is always null is the common failure. The field name in your class must match the JSON exactly, including case, and a mismatch is silent.

## The reserved word problem

Apex will not let you declare a property called `currency`, `date`, `group`, `limit` or a dozen others, and plenty of APIs use exactly those names. The fix is not a workaround, it is a supported pattern: deserialise into a map, rename, and re-serialise.

```apex
String fixed = body.replace('"currency":', '"currencyCode":');
Payment p = (Payment) JSON.deserialize(fixed, Payment.class);
```

A string replace is crude and works when the name is unambiguous. When it is not, go through `Map<String, Object>` and build the object by hand, which is more code and cannot be fooled by the same substring appearing in a value.

## Numbers, which cause most of the rest

JSON has one number type. Apex has four. `Integer`, `Long`, `Decimal` and `Double` all round-trip differently, and a currency value declared as `Double` will eventually show a rounding error in a total someone cares about. Declare money as `Decimal`, always. A blind cast out of an untyped map is where this turns into [Invalid conversion from runtime type](https://trailassembly.com/invalid-conversion-from-runtime-type/).

A number sent as a string, `"amount": "45.00"`, is not a number to the parser, and it is extremely common from systems whose own types are loose. Declare the property as `String` and convert yourself, or fix it at the sender. Do not declare it as `Decimal` and hope.

## Nulls and missing fields are not the same

A field absent from the JSON leaves your property at its default, which for an object is null and for a primitive wrapper is also null. A field present with an explicit `null` does the same thing. So after deserialisation you cannot tell “they did not send it” from “they sent it empty”, and for a PATCH-style integration that distinction is the whole semantics. If you need it, parse to `Map<String, Object>` and check `containsKey`.

## Dates

Apex expects ISO 8601. A date in any other format fails to deserialise into a `Date` or `Datetime`, and the message points at the position rather than explaining the format. Declare the property as `String` and parse explicitly when the source is not reliably ISO, because a failed parse you control gives a better error than a deserialisation that stops the whole payload.

## Frequently asked

**Can I deserialise directly into an sObject?** Yes, and field names must be API names. It works well for simple cases and badly for anything with relationships.

**How do I handle an array at the top level?** Cast to `List<YourType>`: `(List<Order>) JSON.deserialize(body, List<Order>.class)`.

**Is there a size limit?** The deserialised object counts against [heap](https://trailassembly.com/heap-size-too-large/), which is 6 MB synchronously and 12 MB asynchronously. A large payload is a heap problem before it is a parsing problem.

---

Source: https://trailassembly.com/apex-json-deserialization-errors/
Trail Assembly — independent Salesforce write-ups.
