Skip to content

Dealing With the Answer

Module 3 of 8 in the chapter Salesforce as an API Consumer, chapter 9 of the Integration Architect course. Each module stands on its own. Previous: The Six Lines of a Callout · Next: Speed Limits

The question this module answers: Once the other system answers, how does Salesforce turn that answer into something it can safely use?

The six lines that make the callout are the straightforward part. Dealing with the answer is actually the biggest lift in integrations. Three questions, in order: did it work, what shape is it in, and how do I turn it into Apex?

You should always, always, always test the status code. Before you read a single character of the body, res.getStatusCode() tells you which kind of answer you have. Chapter 3 taught the families, and our HTTP Status Codes page lists every code you’re likely to meet. Here the families decide what your code does:

  • 2xx: it worked. Read the body, if there is one. A 204 has none.
  • 4xx: you sent something wrong. Sending it again won’t help. Log it, and get a person to look.
  • 5xx: their side failed. It may work later. This is the one to retry, carefully (chapter 14).
  • Anything you didn’t expect: treat it as a failure, not a success.

And wrap the whole thing in a try and catch. The other system can fail in ways that never reach a status code: a timeout, a closed connection, a body that won’t parse.

Don’t assume. On one integration, I sent JSON out and got XML back. The contract said one thing, and the answer did another. So for every call, know the serialization going out and the serialization coming back, and check the answer’s Content-Type header before you parse.

Three shapes are common:

  • JSON: most of what you’ll see today.
  • XML: rarer, and usually a legacy system. Apex has XmlStreamWriter to write it and XmlStreamReader to read it, one element at a time. Know the names; you can always look up the methods.
  • A file: a PDF, an image, a document. Don’t read it as text. res.getBodyAsBlob() gives you the binary large object, a blob, as it came.

Turning JSON into Apex is the transporter from chapter 3 working in reverse: the stream comes in, and you rematerialize it as objects. There’s a spectrum of ways to do it, and the architect’s job is to pick the right point on it.

One line, into a class that matches. You write a small Apex class, a model, that matches the JSON exactly. Not an sObject; just an Apex structure. Then one line rematerializes the whole answer:

RedrockShipment shipment = (RedrockShipment) JSON.deserialize(res.getBody(), RedrockShipment.class);

It’s simple, it’s type safe, and it’s easy to maintain. This is the fixed deserialization from my JSON Jamboree post, and it’s the mirror of chapter 8’s contract classes. For simple, stable structures, I’d use it every time.

It works great until it doesn’t. And when it stops working, it comes crashing down. Three things bring it down:

  • A field named like an Apex reserved word. You can’t declare a variable with that name, so the class can’t match. The answer a student gave in my class is the one I’d use in production: deserialize it untyped, JSON.deserializeUntyped(...), into a map of names to values, and pick out what you need.
  • A shape that won’t fit a class. One flight-data service I called sent each plane as a list of values where the meaning was the position in the list: no names, just first, second, third. No class matches that. I wrote my own parser with JSONParser, walking the tokens one by one, about 165 lines of code. It works, but it’s fragile code that can break when they change anything.
  • A payload too big for memory. Everything you deserialize lives in the Apex heap. Winter ‘27 raised it from 6 MB to 10 MB for synchronous transactions and from 12 MB to 25 MB for asynchronous ones (The Salesforce Developer’s Guide to the Winter ‘27 Release (opens in a new tab), checked 2026-10-31). Bigger answers than that don’t belong in a callout. Ask for less, page through it, or move the data another way (chapter 10).

And when the fields keep changing, the far end of the spectrum is a mapping kept as data: a table in custom metadata that says which field in the answer goes to which field on the record, so a new field is a change to a record, not a deployment. Chapter 3 shows where that mapping lives.

From one line to a parser From one line to a parser: four ways to turn a JSON answer into Apex, simplest on the left. One line, into a matching class, JSON.deserialize, for simple, stable answers, in navy and tagged start here. When a name won't fit, untyped, into a map, JSON.deserializeUntyped, for a reserved word or a loose shape. When the shape won't fit, your own parser, JSONParser, token by token, fragile. When it keeps changing, a mapping kept as data in custom metadata, so fields change without a deployment. The first two are simpler and type safe; the last two are more flexible and more code. Underneath: one object and 10 fields, one line; twenty objects with 100 fields each, build the dynamic parser at the beginning. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.57.1", "built": "2026-10-31", "id": "DGM-INT-SC-03", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.5.0", "theme": "stevetecharc 1.4.0"} SIMPLER, TYPE SAFE MORE FLEXIBLE, MORE CODE One line, into a matching class JSON.deserialize; for simple, stable answers start here Untyped, into a map JSON.deserializeUntyped; a reserved word, a loose shape Your own parser JSONParser, token by token; fragile A mapping kept as data custom metadata; fields change without a deployment a name won't fit the shape won't fit it keeps changing One object and 10 fields: one line. Twenty objects with 100 fields each: build the dynamic parser at the beginning. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

From one line to a parser. Move right only when the answer makes you.

So which one? As an architect, you need to look into the future. If there’s one object and 10 fields, a class and one line, and you’re done. If they’re telling you there will be 20 objects with 100 fields each, and more coming, I’d invest the time to build the dynamic parser at the beginning. Rewriting a fixed integration under pressure, a class and a deployment for every new field, costs far more than building it right once.

While you’re building, you’ll print the whole answer to the debug log to see what came back. Take that out before production. If the service returns personal data, a patient’s details, a recipient’s address, then the moment someone turns on a debug log, that data is sitting in the log for anyone who can read it. If you really need the payload in a log, mask the sensitive fields first. And huge payloads in debug logs slow Salesforce down too. Chapter 14 designs the integration log that should hold what you need instead.

In 2023 I made a video on three ways to read a JSON answer in Apex, including a mapping kept in custom metadata. The note under the video says what’s still right and what’s changed since.

STA 1.6 · JSON Jamboree: Fixed vs. Dynamic Deserialization Showdown for Apex Callouts · 19:08 · published 2023-09-04 · plays from YouTube when you click it · Watch on YouTube · More videos: Salesforce Integration Architecture Videos
  • What the code does for each status family, written down: read, log for a person, retry, or fail.
  • The format each call sends and receives, checked, not assumed.
  • The point on the spectrum: a matching class, untyped, a hand-written parser, or a mapping in custom metadata; chosen by how much the answer will change, before the first line is written.
  • The size of the answer against the heap, and what happens when it’s too big.
  • What is never logged, and what is masked if it must be.