# Fixing Epicor BusinessObjectException: BPM Directive Errors

> Source: https://netray.co/erp-help/epicor-businessobjectexception-bpm-directive-error
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: ERP Help

## Short answer

A BusinessObjectException that says "A Business Process Management (BPM) directive has raised the following error" means a directive on that business object stopped the transaction, either deliberately via a Raise Exception widget or accidentally via an unhandled .NET error in Custom Code. Expand the InnerException on the error dialog to see the real message and the directive name, then open BPM Designer for that object and method to find the widget that fired.

## Kind

error

## System

epicor

## Erp

Epicor Kinetic / Epicor ERP 10

## Module

BPM / Customization

## Query

Epicor BusinessObjectException: A Business Process Management (BPM) directive has raised the following error

## Alternate Queries

- Epicor BPM custom code exception not caught
- Epicor Post-Processing directive causing update to fail
- how to debug BusinessObjectException in Epicor BPM
- Epicor Raise Exception widget generic error message

## Applies To

Epicor ERP 10.1, 10.2, Epicor Kinetic 2021.1 through 2024.2

## Steps

### Trace and fix the BPM error

**Items:**

Click Details (or the yellow warning triangle) on the client error dialog and expand InnerException - this usually names the failing directive and, for a Raise Exception widget, the exact message text.

Note the business object and method in the stack trace, for example SalesOrder.Update or JobEntry.ChangeJobEngineered, and open BPM Designer for that object and method.

List the directives in execution order: Pre-Processing runs first, then the base method logic, then Post-Processing directives, then Data Directives on the updated table.

If a Raise Exception widget fired, search the directive canvas for that literal message string to find the branch and condition that triggered it.

If it is Custom Code, open the C# widget and look for null references on ttResults tables, a call to another business object without proper transaction scope, or Exception.Publish with no caught inner error.

In a test environment, uncheck Enabled on the suspect directive and repeat the transaction to confirm it is the cause before changing anything in production.

Check the condition widgets feeding the failing branch - most directives only run for specific field changes, row types, or company/plant combinations.

Fix the logic, wrap risky calls in try/catch with a controlled Exception.Publish(new Ice.BLException(...)) message, then re-enable and retest with a full regression pass, not just the one failing scenario.

## Sections

### Why BusinessObjectException wraps BPM errors

**Body:**

Epicor's service layer catches everything a BPM directive throws and re-wraps it as a single BusinessObjectException so the client can show a consistent dialog, regardless of whether the failure came from validation logic, a Raise Exception widget, or an unhandled .NET exception in Custom Code. That consistency is convenient for the UI but unhelpful for debugging, because the top-level message is generic and the real cause sits one or two levels down in InnerException.

In the Epicor Kinetic web client the dialog usually shows a short summary with a Details link; in the classic smart client it is the red error window with an expandable stack. Either way, do not trust the first line - it is almost always "the operation could not be completed" or similar, and the actual widget message or .NET exception text is further down.

### Pre-Processing vs Post-Processing vs Data Directives

**Body:**

Pre-Processing directives run before the base method logic and can prevent the update outright, which is where most deliberate validation ("credit hold", "missing required field") lives. Post-Processing directives run after the base method succeeds and after the change is in the in-memory dataset, which is where notification, integration trigger, and cross-object logic usually sits - these can still roll back the whole transaction if they throw.

Data Directives (Standard, In-Transaction, In-Transaction Data) fire on the actual table write and are the right place to look when the error only appears on save/commit rather than on the field change that triggered it. Knowing which layer you are in narrows the search dramatically instead of reading every directive on the object.

### Common Custom Code culprits

**Body:**

The most frequent causes are: a null reference on a ttOrderHed, ttJobHead or similar typed dataset row when the code assumes a row exists that was filtered out earlier in the same directive; a synchronous call to another business object (for example calling SalesOrderSvc from inside a JobEntry directive) without checking that the target company/plant context matches; and a LINQ query against the dataset that throws when the expected row simply is not present on this transaction.

**Code:**

// Defensive pattern for Custom Code widgets
var row = ttOrderHed.FirstOrDefault(r => r.RowMod != "D");
if (row == null)
{
    // do not assume the row exists - return or branch instead of indexing [0]
    return;
}

### Reproducing safely and tracing execution

**Body:**

Reproduce in a test or pilot database, not production. Epicor's Admin Console and the System Monitor / BPM trace log show widget-by-widget execution for a given session, which is far faster than re-reading the directive canvas from memory when a directive has ten or more widgets across several branches.

If the directive was recently changed, check the object's revision history or ask whoever last touched it in BPM Designer - a condition widget that used to exclude a scenario is the single most common regression after a directive edit.

## Pitfalls

Editing the directive at the wrong layer - Epicor lets directives be scoped company-wide or per-site, and fixing the one you can see is not always the one that fired.

Disabling a directive directly in production to confirm the cause instead of using a test environment - this removes real validation for every other user while you test.

Trusting the top-level error message instead of expanding InnerException, which wastes time chasing the wrong business object.

Fixing only the reported scenario without checking whether the same null-reference or scope bug exists in other branches of the same directive.

Forgetting that Post-Processing and Data Directive failures roll back the entire transaction, so the user sees the error on save even though the actual bad logic ran well after the field they last touched.

Not re-testing integration-triggered paths (BPM firing from a BAQ-driven update, REST call, or EDI import) after a fix that was only verified through the UI.

## Ai Angle

ERPray, grounded in the Epicor object model and the BPM directive source for a given tenant, can be asked "why does updating this sales order line raise a BusinessObjectException" and answer by walking the actual directive chain - which Pre-Processing, Post-Processing and Data Directives exist on SalesOrder.Update, in what order, and which condition widgets gate each one - rather than a support engineer re-opening BPM Designer from scratch. It still leaves the fix and the re-test to a developer; it shortens the hour of tracing that usually happens before anyone touches code.

## Faq

### Where do I see the full InnerException text in the Kinetic web client?

**Answer:** Click the error notification, then Details or the expand arrow on the dialog. If the dataset is truncated, the System Monitor entry for that request (Actions > System Monitor) usually carries the same stack trace in full and is easier to copy from.

### Can I tell from the error alone whether it came from a Raise Exception widget or Custom Code?

**Answer:** Usually yes. A Raise Exception widget produces a clean, short business message with no .NET stack trace beneath it. Custom Code failures show a .NET exception type (NullReferenceException, InvalidOperationException, and so on) with a line-numbered stack, which is the giveaway you are looking at unhandled code rather than deliberate validation.

### Why does the same transaction fail in one company but not another?

**Answer:** BPM directives can be scoped per company and per site. Compare the directive list for both companies in BPM Designer - a directive that is Enabled and configured in one company but missing or disabled in the other is the usual explanation.

### Is it safe to just disable the failing directive?

**Answer:** Only after confirming what business rule it enforces. Directives frequently exist to prevent bad data (negative inventory, missing GL segments, out-of-sequence job operations); disabling one to make an error go away can trade a visible error for a silent data problem later.

## Search terms

Epicor BusinessObjectException, Epicor BPM directive error, Epicor Raise Exception widget, Epicor Custom Code debugging, Epicor Post-Processing directive fails, Epicor Data Directive error, BPM Designer troubleshooting Epicor, Epicor Kinetic error dialog InnerException, Epicor SalesOrder.Update error, Epicor BPM trace log

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
