---
title: "I reported a duplicate-rule problem that did not exist"
description: "A test failed with DUPLICATES_DETECTED, so the rules were obviously set to block. They were not. Database.DMLOptions carries allowSave, but setOptions does not carry the duplicate-rule header — only the Database.insert overload does, and by default that overload does not throw when the save fails for some other reason."
source: "https://noconaconsulting.com/blog/duplicate-rule-header-setoptions/"
---

# I reported a duplicate-rule problem that did not exist

_Build log · Doorvana · Garage door manufacturing & service_

A test failed with DUPLICATES\_DETECTED, so the rules were obviously set to block. They were not. Database.DMLOptions carries allowSave, but setOptions does not carry the duplicate-rule header — only the Database.insert overload does, and by default that overload does not throw when the save fails for some other reason.

Nocona Consulting · October 5, 2026 · 6 min read

A test inserted a record that looked like one already in the org, and the save was rejected. The conclusion was obvious: the duplicate rules are set to block rather than warn, which is too aggressive for an intake process where a second person at the same address is a normal thing that happens.

I wrote that up as a finding. A change was agreed on the strength of it.

Then I retrieved the metadata before touching anything, and the rules were already configured exactly the way I was about to “fix” them. The system was right. The test was wrong. This is the write-up of the thing I got wrong, because that is the more useful post.

## What the rules actually said

force-app/main/default/duplicateRules/Account.Standard\_Account\_Duplicate\_Rule.duplicateRule-meta.xml

```xml
<DuplicateRule xmlns="http://soap.sforce.com/2006/04/metadata">
    <isActive>true</isActive>
    <actionOnInsert>Allow</actionOnInsert>
    <actionOnUpdate>Allow</actionOnUpdate>
    <operationsOnInsert>Alert</operationsOnInsert>
    <operationsOnUpdate>Alert</operationsOnUpdate>
</DuplicateRule>
```

`actionOnInsert=Allow` with `operationsOnInsert=Alert` is an alerting rule: warn the user, let them save anyway. All three standard rules in the org were set that way. Nobody had configured anything to block. There was no misconfiguration to find.

## So why was the save rejected?

Because an alerting rule still rejects a save that has not opted in, and Apex has no user to show a warning to.

That is the piece I had skipped over. “Alert” describes an interactive experience — a person sees a warning and clicks through it. A programmatic caller has no equivalent moment, so the platform demands the equivalent statement up front: pass the **duplicate-rule header** saying save it anyway. Without it, an alerting rule and a blocking rule behave identically from Apex, which is exactly why a failed insert tells you nothing about which one you have.

I had passed the header. That is what made me confident. It just never arrived.

## The two ways to say it, one of which does nothing

three variants, run side by side

```apex
// 1 — plain insert. Rejected: DUPLICATES_DETECTED
insert acct;

// 2 — options set on the record, then a DML statement.
//     Compiles. Runs. Rejected: DUPLICATES_DETECTED
Database.DMLOptions opts = new Database.DMLOptions();
opts.DuplicateRuleHeader.allowSave = true;
acct.setOptions(opts);
insert acct;

// 3 — options passed to the Database.insert overload. Saves.
//     Note the return value: this overload allows partial success and
//     reports failure in SaveResults rather than throwing. See below.
Database.DMLOptions opts2 = new Database.DMLOptions();
opts2.DuplicateRuleHeader.allowSave = true;
List<Database.SaveResult> results =
    Database.insert(new List<Account>{ acct }, opts2);
```

Variant 2 is what my test did, and it is the one that looks most like idiomatic Apex. `Database.DMLOptions` is a real class, `DuplicateRuleHeader.allowSave` is a real property, `setOptions()` is a real method that really does attach options to the record, and the combination is accepted by the compiler without a murmur. Other headers set that way — assignment rules, email headers — do carry.

**`setOptions()` does not carry the duplicate-rule header.** Only the explicit `Database.insert(records, dmlOptions)` overload honours it.

The failure mode is silence, not an error

Nothing warns you. The compiler accepts it, the runtime accepts it, the option is genuinely set on the record, and the save simply fails as though you had never asked. The resulting `DUPLICATES_DETECTED` is indistinguishable from the one you get when you pass nothing at all, which is what makes it so easy to read as a statement about the org’s configuration.

## Correction: the overload has a silence of its own

Adding this some days later, because the fix in this post went on to cost me an afternoon.

`Database.insert(records, dmlOptions)` is the right call for the duplicate-rule header. It is also the overload that **does not throw**. There is no `allOrNone` argument in that signature — partial success is governed by `DMLOptions.optAllOrNone`, which defaults to `false` — so unless you set it, partial success is permitted, and a failed save comes back as a `Database.SaveResult` with `isSuccess() == false` rather than as a `DmlException`.

Swapping a plain `insert` statement for it therefore does two things, and only one of them is the thing you wanted:

what the swap actually changes

```apex
// Before: carries no duplicate-rule header, and throws loudly on any failure.
insert lead;

// After: carries the header — and now fails silently on everything else.
Database.insert(lead, opts);          // returns a SaveResult nobody read

// What it has to be:
Database.SaveResult result = Database.insert(lead, opts);
if (!result.isSuccess()) {
    throw new BookingException(describe(result.getErrors()));
}
```

I found this the expensive way. A sandbox probe checking lead conversion field mapping reported

```plaintext
valid leadId is required: [leadId] — INVALID_CROSS_REFERENCE_KEY
```

which reads as a conversion problem, and I spent a while treating it as one. It was not. The `Lead` insert two lines earlier had already failed on `REQUIRED_FIELD_MISSING: Required fields are missing: [Lead_Channel__c]`, returned a `SaveResult` saying so, and carried on. The conversion was then handed an Id that was null. The error that surfaced was three steps downstream of the error that mattered.

Passing DMLOptions costs you your error handling

The statement form throws and the `Database` overload with `DMLOptions` does not, unless you also set `optAllOrNone = true` on those options. Anywhere you adopt this overload to get `allowSave` across, you have quietly opted into partial success for every other failure mode on that DML as well. Set `optAllOrNone`, or check `isSuccess()` on every `SaveResult` and do something with `getErrors()` — an unread `SaveResult` is the most obedient bug in Apex.

This is the same shape as the mistake the rest of the post is about, which is why it belongs here rather than in a post of its own: the platform accepted everything, reported nothing, and the first visible symptom pointed at innocent code.

Running all three next to each other is what settled it. Two rejected, one saved, same record, same rules, same org, in the same execution. At that point the variable is not the configuration.

## The part I want to keep

The technical finding is useful and it is not the thing I would take away from this.

What actually happened is that a failing test produced a confident, specific, wrong claim about someone else’s system, and that claim very nearly caused a correctly configured org to be changed. The change would have been small and it would have been real: three standard duplicate rules loosened for no reason, on a data-quality control that was doing its job, and nothing afterwards would have pointed back at why.

The only thing that stopped it was reading the configuration before editing it — which I did out of habit rather than doubt, and which took under a minute.

Rule of thumb

A failing test is evidence that something is wrong somewhere. It is not evidence about which component is wrong. Before reporting a configuration problem, retrieve the configuration and read it. The cost is a minute; the alternative is occasionally being confidently wrong in front of someone who will act on it.

There is a version of this job where the consultant’s failed test becomes the client’s configuration problem, and it is a very easy version to fall into, because the test is the thing you can see and the org is the thing you inherited. The discipline that prevents it is small and mechanical: when the evidence points at something you did not write, check the thing you did not write _before_ you say so, not after.

## Practical summary

-   `DUPLICATES_DETECTED` is a statement about a save, not about a rule’s configuration. Read `actionOnInsert` and `operationsOnInsert` on the `DuplicateRule` metadata to learn the configuration.
-   An alerting rule still rejects programmatic saves that do not opt in. That is correct behaviour, not a misconfiguration.
-   Set `DuplicateRuleHeader.allowSave` and pass the options through `Database.insert(records, dmlOptions)`. Do not rely on `setOptions()` for this header.
-   That overload takes no `allOrNone` argument. With `DMLOptions.optAllOrNone` at its default of `false` it allows partial success and returns `Database.SaveResult` instead of throwing; set `optAllOrNone = true` to make it all-or-nothing. Check `isSuccess()` on every result, or adopting it to fix duplicates will lose you the error handling you had.
-   If a test in a data-loading or intake path fails on duplicates, suspect the test’s DML form before suspecting the rules.

The org was fine. The test was not. That is a better outcome than the reverse, but only because it was checked — and it was nearly not.

-   Apex
-   Duplicate rules
-   Testing
-   Metadata API
-   Data quality

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. [More on the work page](https://noconaconsulting.com/work/).

## FAQ

### Why does allowSave not work when set through setOptions in Apex?

Because the duplicate-rule header is not carried by the record's options when DML is executed as a statement. Assigning a Database.DMLOptions instance with DuplicateRuleHeader.allowSave = true via sObject.setOptions and then calling insert compiles and runs, but the duplicate rules still reject the record. The header is honoured only by the Database.insert(records, dmlOptions) overload that takes the options as an explicit argument.

### What does DUPLICATES_DETECTED mean in Salesforce?

It means an active duplicate rule matched the record being saved and the save was not permitted to proceed. It does not by itself prove the rule is configured to block. The same status code appears when a rule is configured to alert and the caller has not passed the duplicate-rule header that says save anyway, which is a property of the calling code rather than of the rule.

### How do you check whether a Salesforce duplicate rule blocks or alerts?

Retrieve the DuplicateRule metadata and read actionOnInsert and operationsOnInsert. actionOnInsert=Allow with operationsOnInsert=Alert is an alerting rule that permits the save when the caller opts in. actionOnInsert=Block is a blocking rule. Reading the metadata takes seconds and is authoritative, where inferring the setting from a failed save is not.

### Does an alerting duplicate rule still reject an Apex insert?

Yes, unless the caller explicitly opts in. An alert rule lets an interactive user see the warning and choose to save anyway. Apex has no user to ask, so a plain insert is rejected with DUPLICATES\_DETECTED and the caller must pass Database.DMLOptions with DuplicateRuleHeader.allowSave set to true through the Database.insert overload to express the same choice.

### Does Database.insert with DMLOptions throw an exception when the save fails?

Not by default. The Database.insert(records, dmlOptions) overload takes no allOrNone argument; partial success is controlled by DMLOptions.optAllOrNone instead, which defaults to false. Left at that default, the call allows partial success and reports failures by returning Database.SaveResult objects instead of throwing a DmlException. Code that switches from a plain insert statement to this overload in order to pass the duplicate-rule header silently loses its error handling at the same time, because the statement form throws and the overload, with optAllOrNone unset, does not. Setting optAllOrNone to true on the same DMLOptions makes the call all-or-nothing again. Every call has to check isSuccess on each SaveResult and act on getErrors, or a failed save looks exactly like a successful one and the missing record surfaces later as a confusing error somewhere downstream.

### What should be checked before reporting a configuration problem to a client?

Read the configuration. A failing test is evidence that something is wrong somewhere, not evidence about which component is wrong. Retrieving the relevant metadata before raising the finding costs a minute and distinguishes a genuine misconfiguration from a defect in the test, which matters because a confident wrong report can lead to a correctly configured system being changed.
