---
title: "CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY: the wrapper that hides the real error"
url: "https://trailassembly.com/cannot-insert-update-activate-entity/"
topic: "Apex and Development"
published: "2026-09-09T08:57:10+00:00"
updated: "2026-10-06T13:51:17+00:00"
error_codes: "CANNOT_EXECUTE_FLOW_TRIGGER, CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, CANNOT_UPDATE_CONVERTED_LEAD, FIELD_CUSTOM_VALIDATION_EXCEPTION, NullPointerException"
---

# CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY: the wrapper that hides the real error

**The short version:** this status code is a wrapper. The real failure is a trigger, [validation rule](https://trailassembly.com/validation-rule-vs-flow/), or flow underneath it, and its message is almost always appended after the wrapper text. Read past the colon and the class name — the sentence at the end of the string is the actual error, and that is what you fix.

## The documented definition is not the whole story

The SOAP API reference gives `CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY` as “You don’t have permission to create, update, or activate the specified record.” Read literally, that sends you to Profiles and [Permission Sets](https://trailassembly.com/permission-sets-after-cancellation/), and in a minority of cases that is right.

In practice you see it most often when a trigger threw an uncaught exception, an `addError()` fired, or automation on the object refused the save. The platform reports that the record could not be created or updated, then attaches whatever the underlying component said. The skill this error demands is parsing, not permission auditing.

## Anatomy of the message

A real one from a Data Loader run:

```apex
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY: OpportunityTrigger: execution
of AfterUpdate caused by: System.NullPointerException: Attempt to
de-reference a null object: Class.OpportunityHandler.rollUpAmounts:
line 47, column 1
```

Each fragment tells you something different:

| Fragment | What it tells you |
| --- | --- |
| CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY | Wrapper. No diagnostic value — skip it. |
| OpportunityTrigger | The trigger that failed. Your entry point. |
| execution of AfterUpdate | The trigger context. Narrows which block ran. |
| System.NullPointerException: ... | The actual error. This is the bug. |
| Class.OpportunityHandler.rollUpAmounts: line 47 | Where to open your editor. |

Three variants come up regularly, and they route to different places.

**A trigger with a stack trace**, as above — a real Apex bug. Go to the named class and line.

**A trigger with a clean sentence and no stack trace** — that is an `addError()` call, which the Apex reference describes as marking “a trigger record with a custom error message and prevents any DML operation from occurring.” Intentional rejection, not a crash. Find the call in the named trigger’s handler and read the condition guarding it.

**No component name at all**, just the wrapper and a short message. This is where the documented meaning applies: check whether the user has Create or Edit on the object, whether the record is locked by an approval process, and whether the record type is assigned to their profile.

## When the message is truncated, get the full one

Several clients cut the string short, which is how people end up holding a wrapper and nothing else.

**From Apex, use partial success and read the results.** The Apex Developer Guide notes that with `allOrNone` false, “if a record fails, the remainder of the DML operation can still succeed. Your application can then inspect the rejected records and possibly retry the operation.”

```apex
List<Database.SaveResult> results = Database.update(records, false);

for (Integer i = 0; i < results.size(); i++) {
    if (results[i].isSuccess()) {
        continue;
    }
    for (Database.Error e : results[i].getErrors()) {
        System.debug(LoggingLevel.ERROR,
            'Id: ' + records[i].Id
            + ' | code: ' + e.getStatusCode()
            + ' | fields: ' + String.join(e.getFields(), ',')
            + ' | message: ' + e.getMessage());
    }
}
```

`getMessage()` returns the whole string. `getFields()` is the shortcut when the cause was a field-targeted `addError()` — it hands you the field name with no parsing.

**From a debug log**, the exception appears in full with its stack trace, unwrapped. This is the most reliable source. Setup → Debug Logs → New, add the user, reproduce, then search the log for `FATAL_ERROR` or `EXCEPTION_THROWN`.

**From the Apex exception email.** An uncaught exception in a trigger mails the org’s Apex exception recipients with the untruncated exception and stack trace. Setup → Apex Exception Email is worth configuring before you need it.

**From Bulk API 2.0**, request the failed-results file for the job rather than reading the job status summary. The summary counts failures. The results file carries the messages.

## Causes, most frequent first

**An uncaught exception in a trigger.** Null pointer, list index out of bounds, a `String` to `Decimal` conversion, a SOQL query returning zero rows into a single-row assignment.

The most confusing version is `List has no rows for assignment to SObject`. It appears when a trigger does `Account a = [SELECT ... WHERE ...]` and the query matches nothing. It works in every test and every manual save, then fails the first time a record lacks the parent the query assumed. Query into a list and check `isEmpty()`.

**An intentional addError() you did not know about.** Someone implemented a business rule in Apex rather than as a [validation rule](https://trailassembly.com/validation-rule-vs-flow/). It reads like a validation error but does not appear in Object Manager → Validation Rules, so people look in the wrong place first. Grep for a distinctive fragment of the message:

```apex
sf project retrieve start --metadata ApexTrigger ApexClass Flow
grep -rn "cannot be closed while" force-app/
```

If the text lives in a managed package you cannot search, the trigger name in the message carries a namespace prefix. That tells you to check the package’s bypass settings or open a vendor case instead of digging further.

**A record-triggered flow failing on the object.** A flow failure surfaces under this wrapper or under [`CANNOT_EXECUTE_FLOW_TRIGGER`](https://trailassembly.com/cannot-execute-flow-trigger/) depending on how it failed. If the message names a flow, or a flow error email arrives at the same moment as the DML failure, treat it as a flow problem and check both the before-save and after-save flows in Setup → Flows.

**Genuine permission and record-state problems.** These match the documented definition and are worth ruling out quickly because the check is fast: no Create or Edit on the object. The record locked by an approval process. The record type not assigned to the user’s profile. Or a converted Lead, which cannot be updated at all and has its own code, `CANNOT_UPDATE_CONVERTED_LEAD` — “a converted lead couldn’t be updated.”

**A trigger blowing a governor limit.** SOQL or DML in a loop, or CPU time exceeded. The limit exception is what the wrapper carries. These are load-dependent, which is why they appear on a 200-record batch and never in a single-record UI save. If the error only shows up during data loads, suspect this first.

## Where in the save the failure happened

Once you know which component failed, the [order of execution](https://trailassembly.com/order-of-execution/) tells you what state the record was in. The Apex Developer Guide’s sequence puts before-save flows and before triggers ahead of system and custom validation, then the database save, then after triggers, then after-save flows.

Two things follow, and both save real time. An after trigger sees the record as saved, so a failure there rolls back a write that already happened, which is why the whole transaction reverts, including records the trigger itself inserted successfully before failing.

And a [before-save flow](https://trailassembly.com/before-save-vs-after-save/) can change a field value before validation runs, so a validation rule can reject a value the user never typed. When you get a validation-shaped message under this wrapper and the value in the message does not match what was submitted, read the before-save flows.

The guide also notes that during recursive saves the middle steps (workflow, older process-builder flows, roll-up summaries) are skipped. So a failure that reproduces on a direct save but not from a trigger-initiated save is often this.

## Preventing it

The goal is not to stop the wrapper appearing. It is to make the message underneath it one you can act on.

**Make deliberate rejections say why, with data.** An `addError()` reading “Invalid state” costs someone an hour. The same call naming the record, the field, and the value costs thirty seconds.

**Do not catch and rethrow generic exceptions in triggers.** A `catch (Exception e) { throw new MyException('Save failed'); }` destroys the stack trace and leaves you with a wrapper wrapping a wrapper. If you catch, log the original with `e.getStackTraceString()` first.

**Configure Apex exception email recipients now.** One-time setting, and it is the difference between diagnosing a production trigger failure in minutes and asking a user to reproduce it.

**Test at 200 records, not one.** Governor-limit and null-pointer causes both hide behind single-record tests.

One opinion with its tradeoff attached: I put user-facing constraints in validation rules rather than `addError()` wherever the formula language can express them, because a rule is discoverable — it appears in Object Manager, in a metadata retrieve, and in a Tooling API query, so the next admin finds it. The cost is that formula constraints cannot look across records or depend on prior state, so genuinely complex rules still belong in Apex. Split on that line, not on which tool you prefer.

## Frequently asked

### Is this a permissions error or not?

Usually not, despite the documented definition. If the message names a trigger, class, or flow, that component is the cause. Treat it as a permissions problem only when nothing is named after the wrapper.

### How do I see the message that got truncated?

Read `Database.Error.getMessage()` from a `SaveResult`, or pull a debug log and search for `FATAL_ERROR`. Both give the untruncated string with its stack trace.

### Why does one record fail and take the whole batch with it?

A DML statement is all-or-none by default. Pass `false` as `allOrNone` to a `Database` method to let the rest commit. The tradeoff is a partially applied batch, so the job must log what failed and be safe to re-run.

### Can a validation rule produce this code instead of FIELD_CUSTOM_VALIDATION_EXCEPTION?

Yes, depending on where in the save the rule fired and what invoked it. A validation-style sentence under this wrapper is still worth searching for in Object Manager → Validation Rules.

### The error only appears during data loads.

Suspect a governor limit, or an assumption that holds for hand-entered records and not loaded ones. Reproduce with a 200-record test and read the debug log rather than the load’s error file.

---

*Reference: [Core Data Types Used in API Calls](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_concepts_core_data_objects.htm) (SOAP API Developer Guide), [Triggers and Order of Execution](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm), [DML Statements vs. Database Class Methods](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_database.htm), [SaveResult Class](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_database_saveresult.htm), [SObject Class](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_sobject.htm), and [Debug Log](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_debugging_debug_log.htm) (Apex Developer Guide).*

---

Source: https://trailassembly.com/cannot-insert-update-activate-entity/
Trail Assembly — independent Salesforce write-ups.
