Lesson 24 · Error Handling

Error Handling in VB 2026

Write bulletproof applications using Try/Catch/Finally, the Exception class hierarchy, multiple Catch blocks, custom exceptions, structured error logging, and async error handling — so your app recovers gracefully instead of crashing.

Key Takeaway: Wrap risky operations in Try/Catch/Finally. Catch the most specific exception type first (e.g. FormatException before Exception) — VB uses the first matching Catch block. Put clean-up code in Finally: it always runs, whether an exception was thrown or not, even if a Return statement exits the Try block early. Never swallow exceptions silently with an empty Catch — at minimum log the message. Use ex.Message for user-friendly text and ex.ToString() for full diagnostic output including the stack trace.
ex.Message
String
Short, user-readable description of what went wrong.
ex.StackTrace
String
Full call stack at the point the exception was thrown — for diagnostics.
ex.InnerException
Exception
The original exception wrapped by a higher-level one. Drill down with .InnerException?.Message.
ex.GetType().Name
String
The exact exception class name: "FormatException", "IOException", etc.
Throw
Statement
Re-throw the current exception (preserves stack trace) or throw a new one.
Finally
Block
Always executes — use for resource cleanup: Close(), Dispose(), unlock.
When clause
Filter
Catch ex As Exception When condition — adds a Boolean filter to a Catch block.
Custom Exception
Class
Inherit from Exception to create domain-specific exception types.

24.1 Try / Catch / Finally

A Try block wraps the code that might fail. If an exception is thrown, execution jumps immediately to the first matching Catch block. The Finally block runs regardless — it's the right place to release resources that must be freed whether the code succeeded or failed.

TryCatch.vb — Visual Basic 2026
' --- Basic structure ---
Try
    ' Code that might throw
    Dim n = CInt(txtInput.Text)   ' FormatException if not a number
    Dim result = 100 \ n           ' DivideByZeroException if n = 0
    lblResult.Text = result.ToString()
Catch ex As FormatException
    ' Caught first — most specific type
    lblResult.Text = "Please enter a whole number."
Catch ex As DivideByZeroException
    lblResult.Text = "Cannot divide by zero."
Catch ex As Exception
    ' Catch-all — least specific, must be last
    lblResult.Text = $"Unexpected error: {ex.Message}"
Finally
    ' Always runs — even if an exception was thrown or Return was called
    btnCalculate.Enabled = True
    lblStatus.Text = "Ready"
End Try

' --- Try/Catch without Finally ---
Try
    File.Delete(filePath)
Catch ex As IOException
    MessageBox.Show($"Cannot delete file: {ex.Message}")
End Try

' --- Finally is NOT the same as code after End Try ---
' Code after End Try does NOT run if Catch re-throws.
' Finally ALWAYS runs.
Try
    conn.Open()
    RunQuery(conn)
Catch ex As Exception
    LogError(ex)
    Throw   ' re-throw to caller — but Finally still runs!
Finally
    conn.Close()   ' always closes the connection
End Try
Order Matters — Most Specific First

VB uses the first Catch block whose type matches the thrown exception. Always order from most specific to least specific. Putting Catch ex As Exception first would catch everything — including FormatException and DivideByZeroException — and none of your specific handlers would ever run.

Try It — Simulation 24.1: Try/Catch/Finally Explorer

Choose an operation and input. See which Catch block fires, the exact exception type and message, and confirm Finally always runs — even when an exception is thrown.

Try/Catch/Finally Explorer
Operation:
Input value:
Try block:
Catch block fired:
Finally block (always runs):

24.2 The Exception Hierarchy

All exceptions inherit from System.Exception. Knowing which type to catch lets you handle each failure mode precisely. Catching a parent type catches all children too — IOException catches both FileNotFoundException and DirectoryNotFoundException.

System.Exception ├── System.SystemException │ ├── FormatException — CInt("abc"), bad date string │ ├── OverflowException — CInt(9_999_999_999) │ ├── DivideByZeroException — n \ 0 or n / 0 (integer) │ ├── NullReferenceException — obj is Nothing, obj.Method() │ ├── IndexOutOfRangeException — arr(10) when Length = 5 │ ├── InvalidCastException — DirectCast(obj, WrongType) │ ├── ArgumentException │ │ ├── ArgumentNullException — parameter must not be Nothing │ │ └── ArgumentOutOfRangeException — value outside allowed range │ └── ArithmeticException │ └── NotFiniteNumberException — Double.NaN, Infinity ├── System.IO.IOException │ ├── FileNotFoundException — file path does not exist │ ├── DirectoryNotFoundException — folder does not exist │ └── UnauthorizedAccessException — no read/write permission ├── System.Net.WebException — HTTP errors, timeout ├── System.Data.SqlException — database errors └── YourApp.CustomException — your own domain exceptions
Exception TypeCommon CauseTypical Fix
FormatExceptionCInt("abc"), bad dateValidate input; use Integer.TryParse
OverflowExceptionCInt(Long.MaxValue)Use larger type or check range first
DivideByZeroExceptionx \ 0 (integers)Guard with If divisor <> 0
NullReferenceExceptionobj.Method() when obj is NothingCheck If obj IsNot Nothing first
IndexOutOfRangeExceptionarr(100) on short arrayCheck i < arr.Length
FileNotFoundExceptionFile.ReadAllText(missing path)Check File.Exists() first
InvalidCastExceptionDirectCast(obj, WrongType)Use TypeOf … Is or TryCast
ArgumentNullExceptionPassing Nothing to a methodValidate parameters at top of method
Try It — Simulation 24.2: Exception Type Explorer

Select an exception type to see: what triggers it, the right Catch syntax, the recommended prevention strategy, and where it sits in the hierarchy.

Exception Type Explorer

24.3 Prevention: TryParse and Guard Clauses

The best error handling is not needing it. Validate inputs before they fail rather than catching exceptions after. Integer.TryParse, Double.TryParse, DateTime.TryParse, and File.Exists let you test without exceptions. Guard clauses exit early with meaningful messages.

Validation.vb — Visual Basic 2026
' --- TryParse: no exception thrown on bad input ---
Dim n As Integer
If Not Integer.TryParse(txtInput.Text, n) Then
    lblError.Text = "Please enter a whole number."
    Return
End If
' n is now safely parsed — no Try/Catch needed

' --- TryParse for Double, Decimal, DateTime ---
Dim price As Decimal
If Not Decimal.TryParse(txtPrice.Text, price) OrElse price < 0 Then
    ShowError("Price must be a positive number.")
    Return
End If

Dim dob As DateTime
If Not DateTime.TryParse(txtDob.Text, dob) Then
    ShowError("Please enter a valid date (dd/MM/yyyy).")
    Return
End If

' --- Guard clauses: validate all inputs at the top ---
Private Sub SaveCustomer(name As String, age As Integer, email As String)
    ' Guards at the top — exit early if invalid
    If String.IsNullOrWhiteSpace(name) Then
        Throw New ArgumentException("Name cannot be empty.", "name")
    End If
    If age < 0 OrElse age > 150 Then
        Throw New ArgumentOutOfRangeException("age", $"Age {age} is outside the valid range 0–150.")
    End If
    If Not email.Contains("@") Then
        Throw New ArgumentException("Email address is not valid.", "email")
    End If
    ' Safe to proceed
    db.SaveCustomer(name, age, email)
End Sub

' --- File existence check before reading ---
If Not File.Exists(filePath) Then
    MessageBox.Show($"File not found: {filePath}")
    Return
End If
Dim content = File.ReadAllText(filePath)

' --- TryCast instead of DirectCast ---
Dim btn = TryCast(sender, Button)
If btn IsNot Nothing Then
    btn.Text = "Clicked"   ' safe — only runs if cast succeeded
End If
Try It — Simulation 24.3: Input Validator

A registration form that validates each field using TryParse and guard clauses before attempting to save. See exactly which validation fails and the corresponding VB guard code.

Customer Registration — Validation Demo
Name:
Age:
Email:
Price (RM):

24.4 Custom Exceptions

When built-in exception types don't describe your domain error precisely, create your own. Inherit from Exception (or a more specific base) and add properties that carry domain-specific context. This makes errors self-documenting and lets callers catch your exception type specifically.

CustomExceptions.vb — Visual Basic 2026
' --- Define a custom exception ---
Public Class InsufficientStockException
    Inherits Exception

    Public ReadOnly Property ProductName As String
    Public ReadOnly Property Requested   As Integer
    Public ReadOnly Property Available   As Integer

    Public Sub New(productName As String, requested As Integer, available As Integer)
        MyBase.New($"Insufficient stock for '{productName}': requested {requested}, available {available}.")
        Me.ProductName = productName
        Me.Requested   = requested
        Me.Available   = available
    End Sub
End Class

' --- Another custom exception ---
Public Class ValidationException
    Inherits Exception
    Public ReadOnly Property FieldName As String
    Public Sub New(field As String, message As String)
        MyBase.New(message)
        Me.FieldName = field
    End Sub
End Class

' --- Throw and catch custom exceptions ---
Private Sub ProcessOrder(productName As String, qty As Integer)
    Dim stock = GetStock(productName)
    If qty > stock Then
        Throw New InsufficientStockException(productName, qty, stock)
    End If
    DeductStock(productName, qty)
End Sub

Private Sub btnCheckout_Click(...) Handles btnCheckout.Click
    Try
        ProcessOrder("Widget A", CInt(nudQty.Value))
        lblStatus.Text = "Order placed!"
    Catch ex As InsufficientStockException
        ' Access domain-specific properties
        lblStatus.Text = $"Only {ex.Available} units left (you requested {ex.Requested})."
        nudQty.Maximum = ex.Available
    Catch ex As Exception
        LogError(ex)
        lblStatus.Text = "Unexpected error. Please try again."
    End Try
End Sub

' --- Re-throw with inner exception (wrapping) ---
Try
    db.Save(order)
Catch ex As SqlException
    Throw New OrderSaveException($"Failed to save order #{order.Id}", ex)
    '                                                        ↑ inner exception
End Try
Try It — Simulation 24.4: Stock Order with Custom Exception

Try ordering more units than available. The custom InsufficientStockException carries ProductName, Requested, and Available properties — shown in the Catch block output.

Order System — Custom Exception Demo
Inventory (stock levels)
Product:
Quantity:

24.5 Structured Error Logging

Never show raw exception messages to end users, but never discard them either. Log the full details — timestamp, exception type, message, stack trace — to a file or event log so you can diagnose problems after deployment. A simple logger is easy to build in VB.

Logger.vb — Visual Basic 2026
' --- Simple file logger module ---
Module AppLogger

    Private ReadOnly _logPath As String =
        Path.Combine(Application.StartupPath, "app.log")

    Enum LogLevel
        INFO
        WARN
        [ERROR]
    End Enum

    Public Sub Log(level As LogLevel, message As String,
                     Optional ex As Exception = Nothing)
        Dim ts   = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
        Dim line = $"[{ts}] [{level,-5}] {message}"
        If ex IsNot Nothing Then
            line &= Environment.NewLine & $"  Exception: {ex.GetType().Name}: {ex.Message}"
            line &= Environment.NewLine & $"  StackTrace: {ex.StackTrace}"
            If ex.InnerException IsNot Nothing Then
                line &= Environment.NewLine & $"  InnerException: {ex.InnerException.Message}"
            End If
        End If
        Try
            File.AppendAllText(_logPath, line & Environment.NewLine)
        Catch
            ' If logging itself fails, swallow silently (can't log the logger error)
        End Try
    End Sub

End Module

' --- Usage ---
AppLogger.Log(LogLevel.INFO,  "Application started")
AppLogger.Log(LogLevel.WARN,  $"Config file missing, using defaults")
AppLogger.Log(LogLevel.ERROR, $"Order #{orderId} failed", ex)

' --- Global unhandled exception handler ---
Sub Main()
    AddHandler Application.ThreadException, AddressOf OnUnhandledException
    AppDomain.CurrentDomain.UnhandledException += AddressOf OnUnhandledException
    Application.Run(New Form1())
End Sub

Private Sub OnUnhandledException(sender As Object, e As ThreadExceptionEventArgs)
    AppLogger.Log(LogLevel.ERROR, "Unhandled exception", e.Exception)
    MessageBox.Show("An unexpected error occurred. The error has been logged.",
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Sub
Try It — Simulation 24.5: Error Logger

Simulate application events that trigger INFO, WARN, and ERROR log entries. Watch the log fill up with timestamped, structured entries exactly as AppLogger.Log() would write them to app.log.

AppLogger — Structured Error Logging Demo
Custom message:
(log is empty — click a button above)

24.6 GitHub Copilot — Error Handling Patterns

GitHub Copilot — Retry with Exponential Back-off
You typed: ' Write a generic Async Function RetryAsync(Of T) that retries a Func(Of Task(Of T)) up to maxRetries times with exponential back-off, catching transient exceptions (IOException, WebException, SqlException). Log each attempt.'
' Retry with exponential back-off — handles transient failures.
Imports System.Net
Imports System.Data.SqlClient

Public Async Function RetryAsync(Of T)(
        operation   As Func(Of Task(Of T)),
        maxRetries  As Integer = 3,
        baseDelayMs As Integer = 500) As Task(Of T)

    For attempt = 1 To maxRetries
        Try
            Return Await operation()   ' success — return immediately
        Catch ex As Exception When IsTransient(ex) AndAlso attempt < maxRetries
            Dim delay = baseDelayMs * CInt(Math.Pow(2, attempt - 1))
            AppLogger.Log(LogLevel.WARN,
                $"Attempt {attempt}/{maxRetries} failed ({ex.GetType().Name}). Retrying in {delay}ms...", ex)
            Await Task.Delay(delay)
        End Try
    Next
    ' Final attempt — let the exception propagate
    Return Await operation()
End Function

Private Function IsTransient(ex As Exception) As Boolean
    Return TypeOf ex Is IO.IOException OrElse
           TypeOf ex Is WebException OrElse
           TypeOf ex Is SqlException
End Function

' Usage:
Dim data = Await RetryAsync(Of String)(
    Async Function() Return Await httpClient.GetStringAsync(apiUrl),
    maxRetries := 3)
Copilot Chat Prompts for This Lesson

Try these in the Copilot Chat panel while working with error handling:

  • "Write a SafeConvert module with functions SafeToInt, SafeToDecimal, SafeToDate that return Nullable(Of T) using TryParse — returning Nothing on failure instead of throwing"
  • "Create a custom ValidationException class with a List(Of String) Errors property, and a Validate() Sub that collects all field errors and throws it once at the end"
  • "Build a global error handler in Sub Main using Application.ThreadException and AppDomain.CurrentDomain.UnhandledException that logs to a rolling daily file (app_2026-02-24.log)"
  • "Write a unit test using MSTest that asserts a specific custom exception type and its properties are thrown by ProcessOrder when stock is insufficient"

Lesson Summary

  • Wrap risky code in Try/Catch/Finally. Order Catch blocks from most specific to least specific — VB uses the first matching Catch.
  • Finally always executes — whether the Try block succeeded, threw, or used Return. Use it for resource cleanup: close files, connections, and locks.
  • Use ex.Message for user messages. Use ex.ToString() (includes stack trace) for log files. Never show raw stack traces to end users.
  • Prevent exceptions before they happen: Integer.TryParse, File.Exists, TryCast, and guard clauses at the top of methods are cleaner than catching avoidable errors.
  • Create custom exception classes by inheriting from Exception. Add domain-specific properties (e.g. ProductName, Available) so callers get structured error information.
  • Never swallow exceptions silently. Log at minimum the timestamp, level, message, exception type and stack trace. Use a structured logger module with File.AppendAllText.
  • Register a global handler with Application.ThreadException to catch any unhandled exceptions and show a friendly message instead of letting the app crash.

Exercises

Exercise 24.1 — Safe Calculator

  • Build a calculator with two text inputs and an operator dropdown (+, −, ×, ÷, \, Mod, ^)
  • Use Decimal.TryParse for input validation — no Try/Catch for parsing
  • Wrap the operation in Try/Catch catching DivideByZeroException, OverflowException, and Exception separately
  • Show a friendly message per error type; log the full exception to a ListBox "log" panel
  • Copilot challenge: Ask Copilot to "add expression history: save each successful calculation to a List(Of String) and display in a ListBox; double-click to re-run"

Exercise 24.2 — File Reader with Logging

  • Let the user browse for a text file; read it with File.ReadAllText inside Try/Catch
  • Catch FileNotFoundException, UnauthorizedAccessException, and IOException separately with specific user messages
  • Always close the file (use Finally or Using statement)
  • Log every error to a rolling log file named app_yyyy-MM-dd.log
  • Copilot challenge: Ask Copilot to "wrap File.ReadAllText in a RetryAsync helper that retries 3 times with 500ms, 1s, 2s delays for IOException"

Exercise 24.3 — Order Validation System

  • Create OrderException, InsufficientStockException, and InvalidProductException custom exception classes with domain properties
  • Write a ProcessOrder Sub that validates product name, quantity (1–999), and stock level before completing
  • Catch each custom type separately in the button handler and show tailored error messages
  • Add a global handler in Sub Main that logs unhandled exceptions and shows a user-friendly dialog
  • Copilot challenge: Ask Copilot to "add a ValidationException that accumulates all field errors in a List(Of String) and throws once at the end of validation"

Next: Lesson 25 — Object-Oriented Programming

Learn OOP in VB 2026 — Classes, Objects, Properties, Methods, Constructors, Inheritance, Polymorphism, and Interfaces. Build a real-world class hierarchy from scratch.

Continue »

Related Resources


Featured Books

Visual Basic 2022 Made Easy

Visual Basic 2022 Made Easy

by Dr. Liew Voon Kiong

Error handling, file I/O, and robust application patterns covered with complete programs.

View on Amazon →
VB Programming With Code Examples

VB Programming With Code Examples

by Dr. Liew Voon Kiong

Real-world programs using structured error handling and validation throughout.

View on Amazon →