Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

The short version: a trigger or a class it called threw an unhandled exception while your bulk load was running, and the Bulk API reported it against every row in the chunk that was being processed at the time. APEX_ERROR is not a Bulk API problem. It is your Apex, failing on data that the API happened to be feeding it.

"Id","Success","Created","Error"
"","false","false","APEX_ERROR:System.NullPointerException: Attempt to
de-reference a null object

(System Code)
Class.AccountSyncHandler.buildMap: line 42, column 1
Trigger.AccountTrigger: line 5, column 1:  []"

Getting the error file

The job result is the only place the full text exists. In the Setup UI, Bulk Data Load Jobs shows the job and its batches with a link to download the failed records. From the CLI, the job ID gives you the same thing:

sf data bulk results --job-id 750XX0000000000 --target-org myorg

The failure file has one row per failed record, carrying the original row’s columns plus Error. Open it in something that handles embedded newlines correctly, because the stack trace contains them and a naive CSV reader will mangle the file into nonsense.

Why the stack trace stops short

The error column has a length cap, and a stack trace that exceeds it is cut off. Two consequences follow.

The deepest frame is usually preserved and the outer frames are what get lost, so you often see the class and line that threw without seeing the path that reached it. And a long exception message can push the trace out entirely, leaving you with the message and no frames at all.

Go to the debug logs instead, where the full trace lives. Set a trace flag on the integration user before rerunning, then look at the log for the failing transaction. If the load has already run and the logs have rolled off, reproduce it with a much smaller file containing only the failed rows.

Partial failure, and what it means for your data

This is the part that determines how you recover, and it is worth being precise about.

  • A Bulk API batch can hold up to 10,000 records.
  • The platform processes that batch in chunks of up to 200, and your trigger fires once per chunk.
  • An unhandled exception rolls back the chunk it happened in. All 200 of those records fail, including the 199 with nothing wrong with them.
  • Other chunks in the same batch are unaffected. Their records are committed and stay committed.

So after an APEX_ERROR run you have a partially loaded data set. The success file tells you what landed. Do not reload the whole source file on the assumption that nothing worked, and do not assume that a single bad record failed alone.

Finding the bad row

The error file names the chunk, not the record. Every row in the chunk carries the same message, and only one of them is the culprit.

Narrow it with the exception type. A null pointer means a lookup was empty for one row: find the row in the failed set whose lookup field is blank, or whose parent does not exist in the org. A query exception about too many rows means the trigger is not bulkified and the chunk size is the actual cause, not any individual record. A DML exception naming a validation rule points at whichever row violates it.

If the type gives you nothing, bisection is quick. Split the failed rows in half, load one half, and repeat. Twelve rows to two takes three attempts.

Making the trigger survive the next load

An unhandled exception in a trigger is a design choice, whether or not anybody made it deliberately. Two changes remove most of these failures.

Null-check what the trigger dereferences. A map lookup returns null for a missing key rather than an empty collection, and a bulk load is where the missing key finally appears.

Keep the trigger free of anything per-record. One query before the loop, one DML after it. The chunk size is 200 and no amount of careful data cleaning changes that.

Frequently asked

Can I turn off triggers for a bulk load? Not through the API. A custom setting checked by the trigger framework is the usual approach, and it is dangerous for exactly the reason it is convenient: the work the trigger does still needs doing afterwards.

Does the error appear in the Apex exception email? Unhandled exceptions in a trigger do generate the standard notification to the developer address, and that copy is not truncated the way the CSV column is. Check there before rerunning anything.

Why did it work in Data Loader’s serial mode? Smaller batches, different chunk boundaries, and a load slow enough to avoid lock contention. The underlying Apex bug is still there.

Does Bulk API 2.0 behave the same way? Yes for triggers and rollback. It manages batching for you and exposes failed results through the job’s failed records endpoint rather than through per-batch files.