Exception Handling between different modules

As you might know, exception types cannot be referenced between modules such that if you define an Exception Type in one module, you cannot add a reference to it from a consumer module and this is a limitation of how Exception Types are scoped:
- Exception Types are always local to a module.
- If you expose a public action that throws a custom Exception Type, the consumer cannot see or reference that specific type — they just get a generic
Exception
at the consuming side. - This means you lose the ability to distinguish between custom exception types raised inside producer modules.
What if you want to identify the exception type in a consumer module because you need to know what caused the problem? What if you need to provide error codes within the exception itself?
Let’s consider the code we already saw in earlier post and lets focus on Contact_CreateOrUpdate. In that single action, I’m throwing 3 different kind of Exceptions:
- ValidationException – used to indicate user-input validation errors (displayable to the user).
- RecordNotFoundException – triggered when a database record is missing (includes internal troubleshooting details, so it’s not user-facing).
- ConcurrencyException – raised when concurrency conflicts occur (detailed internal message logged, while a generic message is shown to the user).

If we follow the previous pattern, each one of the typed exceptions will be handled as a generic expression in our frontend module.

As you can see we don’t know the Exception Type and we cannot handle it according to it’s type. This is true because User Exceptions cannot be exposed as public, so it cannot be referenced in consuming modules, resulting on an untyped exception where you can see the inner exception message.
We’re kind of stuck. We can do good code (by defining exceptions according to it’s kind) but we cannot leverage from that fact (when code lives in a different modules).
So what can we do?
Let’s create a new library module named ‘TypedExceptions_Lib’ where we:
- Create a static entity to enumerate different Exception Types;
- Create a public ExceptionStruct that contains the ExceptionType, Code and Message;
- Create two public actions:
- ThrowTypedException (Serializes the struct data into the exception message and throws ;
- GetTypedException (returns the struct data);
Step 1: Update existing code by, instead of throwing exceptions, calling ThrowTypedException from the TypedExceptions_Lib module:
Old Code

New Code

By doing this change, instead of directly throwing the typed exception, we’ll be delegating that to our TypedExceptions_Lib module.
Step 2: In our frontend global exception handler, we need to intercept the exception and treat it accordingly:

- Disable default error logging (“Log Error = No”) to avoid clutter.
- Use GetTypedException to extract ExceptionStruct data from the exception;
- Handle each exception type accordingly:
- ValidationException – displayed as a user-friendly warning, without logging.
- UnauthorizedException – shows a generic “You are not allowed to perform this action” message, with logging for auditing.
- ConcurrencyException – displays a generic concurrency error and avoids logging.
To see this in action I’ll force a Concurrency exception to occur…
First I’ll open 2 browser tabs (side by side) containing the same record:

Then I’ll change the name field in the first tab:

Now if I try to change the email field on the last tab without reloading the page, the application will fire the generic message defined for the ConcurrencyException:

Final considerations on this approach:
- Ensure all potential exception types are handled in the global handler; otherwise, users may see raw JSON—not ideal;
- As a result of the exception being thrown by the TypedExceptions_Lib, this will end by adding information to the exception stack trace that must be taken in consideration when analyzing it;
- Remember that
ThrowTypedException
injects serialized JSON into the exception message, so designing reliable parsing logic is essential; - This approach dramatically improves error transparency and maintainability across modules while retaining the ability to classify exception types centrally.