Handling errors in Class Modules.

Debugging errors in VBA Class Modules is notoriously frustrating because Excel’s default development environment obscures where the fault actually occurred. When a class method fails, the Visual Basic Editor frequently halts on the line in your standard module that called the class—rather than highlighting the broken line inside the class itself. Without the right configuration, tracking down errors in properties, custom events, and class lifecycle hooks becomes an exercise in guesswork.

Quick Answer

In the VBA Editor, click Tools > Options, go to the General tab, and under Error Trapping, change the setting from Break on Unhandled Errors to Break in Class Module. This forces VBA to stop execution directly on the offending statement inside the class file instead of the line that invoked it.


The Error Trapping Setting: Why Classes Hide Broken Lines

By default, the Visual Basic Editor is configured to Break on Unhandled Errors. This works well for procedural code in standard modules, but it creates a serious blind spot when working with Object-Oriented VBA:

Default Behavior (Break on Unhandled Errors):
  Standard Module: oInvoice.CalculateTotal  <-- Execution breaks HERE (misleading)
         │
         ▼
  Class Module:    Property Get CalculateTotal()
                   total = subTotal / itemCount   <-- Bug is actually HERE (Division by Zero)

Because the calling procedure did not have an active error handler around oInvoice.CalculateTotal, VBA flags the call site in the standard module as the unhandled failure point. You are left staring at a completely valid procedure call while the actual syntax or logic error remains buried inside the class.


First-Line Fix: Configure Editor Error Trapping

To debug classes effectively, tell the VBA runtime to break at the true source of every exception:

  1. In the Visual Basic Editor (Alt + F11), click Tools > Options.
  2. Select the General tab.
  3. Locate the Error Trapping group box in the upper-right corner.
  4. Select Break in Class Module.
  5. Click OK.
┌────────────────────────────────────────────────────────┐
│ Options: General Tab                                   │
├────────────────────────────────────────────────────────┤
│ Error Trapping                                         │
│   ○ Break on All Errors                                │
│   ● Break in Class Module  <-- Select this option      │
│   ○ Break on Unhandled Errors                          │
└────────────────────────────────────────────────────────┘

Now, when a property or method fails, execution pauses on the exact line within the .cls module, allowing you to inspect local class variables, check references, and step through code using the techniques in Using Watch Windows to track Object states.


Designing Defensive Class Modules

Beyond configuring editor settings, well-structured classes should handle input validation internally and raise meaningful, custom errors back to the calling code.

1. Validate Inputs Inside Property Let Procedures

Never allow invalid data to reach internal class variables. Catch bad data inside property assignment procedures before it causes problems downstream:

' In Class Module: clsEmployee.cls
Private pSalary As Double

Public Property Let Salary(ByVal Value As Double)
    If Value <= 0 Then
        ' Raise a custom business error to the caller
        Err.Raise Number:=vbObjectError + 513, _
                  Source:="clsEmployee.Salary", _
                  Description:="Salary must be greater than zero."
        Exit Property
    End If
    pSalary = Value
End Property

Public Property Get Salary() As Double
    Salary = pSalary
End Property

Using vbObjectError + [Number] ensures your custom error codes do not collide with built-in VBA error codes (numbers 1 through 1000).

2. Managing the Class_Initialize and Class_Terminate Lifecycle

Class lifecycle events have strict operational rules that can cause silent failures if mismanaged:

  • Class_Initialize: Fires automatically when you call Set myObj = New clsMyClass. If an unhandled error occurs during initialization, object instantiation aborts immediately, resulting in Runtime Error 91: Object variable or With block variable not set (The Set keyword trap) at the call site. Always validate prerequisites (such as database paths or worksheet references) inside Class_Initialize.
  • Class_Terminate: Fires when the object reference is destroyed (Set myObj = Nothing or when it falls out of scope). VBA completely suppresses unhandled errors inside Class_Terminate. If your termination code crashes while closing an external file or releasing a database connection, Excel will not notify you; it will simply leak the resource. Wrap all teardown code in defensive error blocks:
Private Sub Class_Terminate()
    On Error Resume Next
    If Not mConnection Is Nothing Then
        mConnection.Close
        Set mConnection = Nothing
    End If
    On Error GoTo 0
End Sub

Diagnostic Branching: Identifying Class Failures

If your class module compiles without errors but misbehaves during execution, work through this diagnostic sequence:

Branch A: The Class Returns Runtime Error 438

If calling a member triggers Runtime Error 438: Object doesn’t support this property or method (Typing errors), the variable was likely declared using generic late binding (Dim myObj As Object).

  • Without explicit early binding (Dim myObj As clsEmployee), VBA cannot verify method names at compile time. A simple typo in the property name will only surface as a crash during execution. Always declare classes with explicit types.

Branch B: Returning Intentional Errors to Worksheet Formulas

If your class module is backing a User-Defined Function (UDF) that outputs results to the worksheet grid, do not use Err.Raise. Raising an unhandled error inside a UDF results in a generic #VALUE! error in the cell.

Branch C: Centralizing Class Error Reporting

For enterprise projects with multiple interdependent classes, do not scatter custom message boxes inside individual property blocks. Let individual classes throw standardized errors up the call stack, and catch them in a single, centralized handler in your main entry module using Building a Global Error Handler: The Err.Number and Err.Description guide.