Common Error Handling Bugs and How to Catch Them
Common Error Handling Bugs and How to Catch Them is a critical topic for any software development team aiming to deliver robust and reliable applications. Poor error handling can transform minor glitc
Common Error Handling Bugs and How to Catch Them is a critical topic for any software development team aiming to deliver robust and reliable applications. Poor error handling can transform minor glitches into catastrophic failures, erode user trust, and lead to significant operational overhead. As QA and test engineers, our role extends beyond merely verifying happy paths; we must actively seek out the myriad ways an application can fail gracefully, or, more often, ungracefully. This guide will meticulously detail common error handling bugs, explain their root causes and user impact, and provide practical strategies for detection, reproduction, and prevention, focusing on both manual and automated testing approaches.
Effective error handling ensures that when things inevitably go wrong—be it an unexpected network drop, an invalid user input, or a backend service outage—the application responds predictably, informs the user appropriately, and ideally, recovers or guides the user towards recovery. Neglecting this crucial aspect of development often results in production incidents, negative reviews, and a perception of instability. By understanding the common pitfalls, we can proactively design our testing strategies to uncover these vulnerabilities long before they impact end-users.
Understanding the Landscape of Error Handling Failures
Before diving into specific bug patterns, it's essential to classify the types of error handling failures we typically encounter. These can broadly be categorized by their impact and origin:
- Silent Failures: The application fails to process an action but provides no feedback to the user. The UI might simply hang, or nothing happens. This is particularly insidious as users are left guessing.
- Crashing/ANR (Application Not Responding) Failures: The application terminates abruptly or becomes unresponsive. This is a severe impact, often leading to data loss or significant user frustration.
- Misleading/Incorrect Error Messages: An error occurs, and a message is displayed, but it's unhelpful, technically obscure, or outright wrong, confusing the user.
- Incomplete State Transitions: An error occurs during a multi-step operation, leaving the application or data in an inconsistent or partially updated state.
- Security Vulnerabilities: Error messages or logging inadvertently expose sensitive system information, aiding potential attackers.
- Performance Degradation: Repeated error conditions, especially those involving retries or resource leaks, can severely degrade application performance.
The Cost of Poor Error Handling
The repercussions of inadequate error handling extend beyond immediate user frustration. Consider the following:
- User Churn: A single frustrating experience can drive users away, especially in competitive markets.
- Reputational Damage: Negative app store reviews or social media complaints can quickly tarnish a brand's image.
- Increased Support Costs: Users encountering unhandled errors will flood support channels, leading to higher operational expenses.
- Developer Productivity Loss: Debugging production issues caused by unhandled errors is time-consuming and often involves sifting through insufficient or misleading logs.
- Security Breaches: Leaked stack traces or configuration details in error messages can be exploited.
Common Error Handling Bugs and Detection Strategies
Here, we'll explore specific error handling bug patterns, detailing their characteristics, how they manifest, and concrete strategies to detect them.
1. The Silent Failure: No User Feedback on Backend Errors
What it is: A user initiates an action (e.g., submitting a form, making a purchase), the request is sent to the backend, but the backend fails (e.g., database error, internal server error). The frontend, however, provides no indication of this failure to the user. The UI might just clear the form, or the loading spinner spins indefinitely, or nothing visibly changes.
Why it happens: Developers often handle the "happy path" (2xx HTTP status codes) but overlook or incompletely handle non-2xx responses (4xx, 5xx). The catch block might be empty, or the UI update logic is only triggered on success.
How it looks to users: User presses "Submit," form clears, but the data isn't saved. User clicks "Add to Cart," but the item doesn't appear. User sees a loading spinner that never resolves. They assume the action succeeded or that the app is slow/broken.
How to reproduce and detect:
- Manual: Use browser developer tools (Network tab) or a proxy tool like Charles Proxy/Fiddler. Intercept a successful API request and modify its response to simulate a 500 Internal Server Error, 400 Bad Request, or 403 Forbidden. Observe the application's behavior.
- Automated:
- API Testing: Write dedicated API tests that assert specific error responses (e.g., a 500 status code) are handled by the calling service/function, not just ignored.
- UI Integration Testing: After triggering an action, assert that an expected error message or state (e.g., "Failed to save data, please try again") is displayed on the UI when the mocked API returns an error. Frameworks like Playwright (for web) or Appium (for mobile) can interact with UI elements and check for text visibility.
- Network Condition Simulation: Simulate poor network conditions or outright disconnections during API calls. Many browser developer tools allow throttling. For mobile, network link conditioners or device settings can simulate this.
How to fix and prevent:
- Code Review Focus: Emphasize review of
try-catchblocks andPromise.catch()chains, specifically checking for UI feedback logic within error handlers. - Standardized Error Handling Component: Implement a global error handler or a reusable UI component for displaying error messages (e.g., a toast notification, an error banner).
- Clear API Contracts: Define expected error responses (e.g., specific error codes, structured error bodies) in API documentation so frontend developers know what to expect and handle.
2. Generic and Unhelpful Error Messages
What it is: An error occurs, and the user receives a message like "An unexpected error occurred," "Request failed," or even a raw stack trace or HTTP status code. This provides no actionable information.
Why it happens: Developers often catch exceptions broadly and display a default message without parsing the actual error details. Or, in an attempt to hide sensitive information, they over-generalize, rendering the message useless. Sometimes, internal backend error messages are directly propagated to the frontend.
How it looks to users: User tries to log in, gets "An error occurred." They have no idea if their credentials were wrong, the server is down, or their internet connection failed. This leads to frustration and support calls.
How to reproduce and detect:
- Manual: Trigger various error conditions (invalid input, missing required fields, network issues, backend errors via proxy as above). Observe the displayed error messages for clarity, conciseness, and actionability.
- Automated:
- UI Text Assertions: In UI tests, trigger an error and assert that specific, user-friendly error messages are displayed. For example, if a "required field" error occurs, assert the message "Please fill in all required fields" is visible.
- Negative Testing: Design test cases specifically to induce known error conditions and verify the corresponding expected error messages.
How to fix and prevent:
- Error Code Mapping: Create a system to map internal technical error codes (e.g., specific HTTP status codes, custom backend error codes) to user-friendly messages.
- Contextual Messages: Ensure error messages provide context. Instead of "Invalid input," say "Email address format is invalid."
- Avoid Raw Technical Details: Never expose stack traces, database errors, or internal server IP addresses directly to the user. Log these details server-side, but present a simplified message to the user.
- Localization Considerations: Ensure error messages are ready for internationalization if the application supports multiple languages.
3. Incomplete State Transitions / Data Corruption on Error
What it is: An operation involving multiple steps or updates to different data stores fails midway, leaving the system in an inconsistent state. For example, an order is created, but payment fails, and the order isn't rolled back, or inventory is decremented but the order isn't finalized.
Why it happens: Lack of transactional integrity. Operations aren't atomicity, or rollback mechanisms aren't implemented or are flawed. Developers might forget to clean up temporary states or revert partial changes if an error occurs after a partial success.
How it looks to users: User places an order, payment fails, but their cart is empty, and they can't re-add items or retry payment. Or, an item appears as "sold out" even though their payment didn't go through. This can lead to significant data integrity issues and customer service nightmares.
How to reproduce and detect:
- Manual:
- Mid-Process Interruption: During a multi-step flow (e.g., checkout, data migration), simulate an error at various points. This can involve killing the application, disconnecting the network, or triggering a backend error via proxy.
- Database Inspection: After an interrupted flow, manually inspect the database to check for partial records, orphaned data, or incorrect status flags.
- Automated:
- Integration Tests with Mock Failures: Write integration tests that simulate failures at specific points in a multi-step process (e.g., mock a payment service throwing an exception after an order is created). Assert that the system state (e.g., database records, user session data) is correctly rolled back or remains consistent.
- End-to-End Tests with State Verification: Implement E2E tests that not only check UI outcomes but also verify backend data integrity after error scenarios.
How to fix and prevent:
- Transactional Design: Use database transactions or distributed transaction patterns (e.g., Saga pattern) for operations that involve multiple interdependent steps.
- Idempotency: Design operations to be idempotent, so retrying them multiple times has the same effect as performing them once.
- Compensation Logic: Implement compensation actions for distributed systems where immediate rollback isn't possible, to clean up partial successes.
- Rollback Mechanisms: Ensure that error handlers explicitly revert any changes made before the point of failure.
4. Application Crashes/ANRs on Specific Inputs or Edge Cases
What it is: The application terminates unexpectedly or becomes unresponsive (ANR on Android, spinning beach ball on macOS, frozen window on Windows) when encountering particular data, specific sequences of actions, or resource exhaustion.
Why it happens: Unhandled exceptions (e.g., NullPointerException, IndexOutOfBoundsException), out-of-memory errors, infinite loops, race conditions, or resource leaks. Often triggered by unexpected data formats, very large inputs, or rapid user interactions.
How it looks to users: The app instantly closes. On mobile, a "App has stopped unexpectedly" dialog appears. On web, the tab might crash or become unresponsive. This is a severe failure mode, leading to data loss and extreme user frustration.
How to reproduce and detect:
- Manual:
- Fuzz Testing: Input random, malformed, or extremely large data into all input fields.
- Edge Case Data: Test with maximum/minimum allowed values, empty strings, special characters, international characters.
- Rapid Interaction: Tap/click rapidly on buttons, switch tabs quickly, perform gestures excessively.
- Resource Depletion: Open many tabs, upload large files, run other demanding apps simultaneously.
- Automated:
- Unit/Integration Tests: Crucial for catching unhandled exceptions in specific code paths. Assert that expected exceptions are indeed thrown and handled.
- Crash Reporting Tools: Integrate crash reporting (e.g., Sentry, Firebase Crashlytics) early in development to catch and log unhandled exceptions in test environments.
- Automated UI Exploration (like SUSATest): Platforms like SUSATest are uniquely suited here. By autonomously exploring the application with various user personas (e.g., "Curious User" tapping every element, "Impatient User" performing rapid actions, "Adversarial User" trying unusual inputs), it can discover unexpected states and trigger crashes that scripted tests, focused on known paths, would miss. SUSATest can identify ANRs and crashes directly and pinpoint the action sequence that led to them.
- Memory/CPU Profiling: Use tools like Android Studio Profiler, Xcode Instruments, or browser performance monitors to identify memory leaks or excessive CPU usage that could lead to ANRs.
How to fix and prevent:
- Robust Input Validation: Validate all user inputs and external data rigorously, both on the client and server side.
- Defensive Programming: Assume external data can be null or malformed. Use null-safe operators, bounds checks, and type assertions.
- Exception Handling: Implement comprehensive
try-catchblocks for all operations that might throw exceptions. Log exceptions properly. - Resource Management: Ensure proper release of resources (file handles, network connections, memory) using
finallyblocks ortry-with-resourcesconstructs. - Concurrency Control: Use locks, semaphores, or atomic operations to prevent race conditions in multi-threaded environments.
5. Inconsistent Error Display Across the Application
What it is: Different parts of the application display error messages inconsistently. Some use toast notifications, others modal dialogs, some inline text. The styling, tone, and placement vary.
Why it happens: Lack of a centralized UI/UX design system for error feedback. Different teams or developers implement error handling independently, leading to fragmentation.
How it looks to users: The application feels unpolished and unprofessional. Users might miss important error messages if the display method changes unexpectedly. It creates cognitive load as users have to learn different error feedback patterns.
How to reproduce and detect:
- Manual:
- Comprehensive Error Scenario Testing: Systematically trigger every possible error condition across the application and document how each error is displayed. Look for variations in styling, position, and interaction.
- UI/UX Review: Conduct a dedicated review of all error states with a focus on consistency.
- Automated:
- Visual Regression Testing: Tools like Percy or Storybook's Storyshots (with image snapshots) can capture screenshots of error states. Any change in the error message's appearance (position, font, color) would be flagged as a regression.
- Component Library Testing: If a design system exists, ensure that all error components adhere to the defined standards through unit tests on the components themselves.
How to fix and prevent:
- Design System for Error States: Establish clear guidelines in the design system for how all types of errors (inline, global, form-level, success/failure notifications) should be presented.
- Reusable UI Components: Develop and enforce the use of a single set of UI components for displaying various error types.
- Centralized Error Service: Implement a frontend service that standardizes how errors are processed and displayed to the user.
6. Security Vulnerabilities Due to Verbose Error Messages
What it is: Error messages, logs, or diagnostic pages expose sensitive information like database connection strings, API keys, server stack traces, internal IP addresses, or environment variables.
Why it happens: Developers often include detailed technical information in error messages for debugging purposes, forgetting that these messages might be exposed to end-users or attackers in production. Lack of sanitization or redaction.
How it looks to users/attackers: A user might see a full stack trace with file paths, database query details, or even snippets of code. An attacker can use this information to understand the system's architecture, identify potential attack vectors, or craft more targeted exploits (e.g., SQL injection, path traversal).
How to reproduce and detect:
- Manual:
- Penetration Testing: Ethical hackers will specifically look for verbose error messages as a starting point for reconnaissance.
- Proxy Inspection: Use a proxy (like Burp Suite) to intercept error responses from the backend. Examine the body for any sensitive information.
- Fuzzing: Intentional malformed inputs or requests can sometimes trigger verbose errors that wouldn't appear in normal usage.
- Automated:
- Security Scanners (SAST/DAST): Static Application Security Testing (SAST) tools can detect patterns in code that might lead to verbose errors. Dynamic Application Security Testing (DAST) tools can actively trigger error conditions and analyze responses for sensitive data.
- Log Monitoring & Alerting: Configure log aggregators to alert on specific keywords or patterns indicative of sensitive data exposure in error logs.
- Automated UI Exploration (like SUSATest): While primarily focused on functional and UX issues, an "Adversarial User" persona in SUSATest might trigger unexpected inputs or sequences that lead to verbose error displays on the UI, which would then be flagged.
How to fix and prevent:
- Generic Public Error Messages: For public-facing errors, always display a generic, user-friendly message. Log the detailed technical error server-side.
- Error Message Sanitization: Implement logic to strip or redact sensitive information from error messages before they are displayed to the user or sent to the client.
- Environment-Specific Configuration: Ensure that verbose error reporting (e.g., displaying stack traces) is *only* enabled in development environments, never in staging or production.
- Principle of Least Privilege: Ensure that the application user or service account has only the necessary permissions, reducing the impact if credentials are leaked.
- Secure Logging Practices: Implement structured logging and ensure sensitive data is masked or encrypted in logs.
7. Incorrect Retry Logic / Infinite Loops
What it is: When a transient error occurs (e.g., network timeout), the application attempts to retry the operation. However, the retry logic is flawed: it retries too aggressively, indefinitely, or with incorrect backoff strategies, leading to resource exhaustion or a denial of service (DoS) for the backend.
Why it happens: Developers implement basic retry loops without considering exponential backoff, maximum retry attempts, or circuit breakers. Sometimes, a non-idempotent operation is retried, leading to duplicate actions.
How it looks to users: The application hangs or becomes very slow. The user might see multiple duplicate notifications or actions (e.g., charged twice for an order). The backend service might become overwhelmed and crash.
How to reproduce and detect:
- Manual:
- Network Interruption/Lag: Simulate intermittent network connectivity issues during operations that involve retries. Observe if the application recovers gracefully or enters a loop.
- Backend Service Failure Simulation: Temporarily take down a backend service or configure it to return frequent 5xx errors.
- Automated:
- Unit/Integration Tests for Retry Policies: Write tests specifically for the retry mechanism, asserting that it uses exponential backoff, has a maximum number of retries, and handles non-retryable errors correctly.
- Performance Testing: Run load tests while simulating backend failures to see if the retry logic exacerbates the problem, leading to resource spikes or cascading failures.
- Monitoring & Alerting: Monitor backend service metrics (CPU, memory, request queues) for unusual spikes during error conditions that could indicate aggressive retry logic.
How to fix and prevent:
- Implement Exponential Backoff: Increase the delay between retries exponentially.
- Set Max Retries: Define a maximum number of retry attempts. After that, fail definitively and inform the user.
- Use Circuit Breakers: Implement a circuit breaker pattern (e.g., Hystrix, Polly) to prevent repeated calls to a failing service, allowing it time to recover.
- Retry-After Headers: If the backend provides
Retry-Afterheaders, respect them. - Idempotency for Retriable Operations: Ensure that operations that might be retried are idempotent to avoid unintended side effects.
8. Accessibility Issues in Error Feedback
What it is: Error messages are visually present but not properly communicated to users relying on assistive technologies (screen readers, braille displays). Or, the error feedback relies solely on color, which is inaccessible to color-blind users.
Why it happens: Oversight in UI development, lack of awareness of WCAG (Web Content Accessibility Guidelines) standards, or insufficient testing with assistive technologies.
How it looks to users:
- Screen Reader Users: An error message might appear on the screen, but the screen reader doesn't announce it, or announces it too late, or out of context. The user might not know an error occurred or where it is.
- Color-Blind Users: An error indicated only by red text might be indistinguishable from normal text.
- Keyboard-Only Users: Error messages might trap focus or be unreachable via keyboard navigation.
How to reproduce and detect:
- Manual:
- Screen Reader Testing: Use a screen reader (e.g., NVDA, JAWS, VoiceOver, TalkBack) to navigate the application and intentionally trigger errors. Verify that error messages are announced clearly, concisely, and immediately.
- Keyboard Navigation: Test navigating and interacting with forms and error messages using only the keyboard (
Tab,Shift+Tab,Enter,Space). - Color Contrast Checkers: Use browser extensions or tools to check color contrast ratios, especially for error indicators.
- SUSATest (Accessibility Persona): SUSATest includes an "Accessibility User" persona that specifically checks for WCAG violations. This persona can identify issues like missing ARIA attributes for error messages, insufficient color contrast, or elements that aren't properly announced by screen readers, making it a powerful tool for discovering these types of error handling bugs.
- Automated:
- Accessibility Linting/Scanners: Tools like axe-core (integrated into Lighthouse, Cypress, Playwright) can scan the DOM for common accessibility violations related to error messages (e.g., missing
aria-liveregions, incorrectaria-describedbyattributes, insufficient contrast). - Unit/Integration Tests for ARIA Attributes: Assert that error components have the correct ARIA attributes and roles.
How to fix and prevent:
- WCAG Compliance: Adhere to WCAG guidelines for error identification and input assistance.
- ARIA Live Regions: Use
aria-live="assertive"oraria-live="polite"to dynamically announce error messages to screen readers. - Semantic HTML: Use appropriate HTML elements (e.g.,
for inputs,for groups) to improve inherent accessibility. - Multiple Cues for Errors: Don't rely solely on color. Use icons, bold text, or explicit error messages in conjunction with color.
- Focus Management: Ensure that when an error occurs, focus can be programmatically moved to the error message or the problematic input field.
9. Lack of Clear Call to Action / Recovery Options
What it is: An error message informs the user that something went wrong, but provides no guidance on what to do next.
Why it happens: Developers focus on identifying the problem but not on helping the user resolve it or recover.
How it looks to users: User sees "Failed to upload file." They don't know if they should try again, check their internet, reduce file size, or contact support. They are stuck and frustrated.
How to reproduce and detect:
- Manual:
- "What Now?" Test: For every error message, ask "What should the user do next?" If the answer isn't immediately obvious from the message itself, it's a bug.
- Scenario-Based Testing: Trigger errors and then assess the available options. Are there "Retry" buttons? Links to help documentation? Contact support options?
- Automated:
- UI Text Assertions: Assert that error messages not only describe the error but also contain actionable phrases like "Please try again," "Check your internet connection," or "Contact support."
- Link/Button Presence Assertions: Verify that relevant buttons (e.g., "Retry," "Go Back," "Contact Us") or links are present in the error state.
How to fix and prevent:
- Actionable Error Messages: Every user-facing error message should ideally include:
- What went wrong.
- Why it might have gone wrong (if known and simple).
- What the user can do next (e.g., retry, check input, contact support).
- Contextual Recovery: Provide recovery options relevant to the specific error. For a network error, offer a "Retry" button. For invalid input, highlight the problematic field.
- Link to Help/Support: For complex errors, provide a direct link to relevant help documentation or customer support.
10. Ignoring Third-Party API Errors
What it is: An application integrates with external services (e.g., payment gateways, social media APIs, mapping services). When these third-party APIs return errors, the application either silently fails, crashes, or displays a generic error without acknowledging the external dependency.
Why it happens: Developers might assume third-party services are always reliable or only handle successful responses. Lack of robust error mapping from external API errors to internal application errors.
How it looks to users: User tries to pay, gets "Payment failed," but the payment gateway's specific error (e.g., "Insufficient funds," "Card expired") isn't communicated. Or, a map feature simply doesn't load without explanation.
How to reproduce and detect:
- Manual:
- Mock Third-Party Services: Use tools like WireMock, Mock Service Worker (MSW), or even simple proxy rules to simulate various error responses (4xx, 5xx, timeouts) from integrated third-party APIs.
- Test with Invalid Credentials/Keys: Use intentionally incorrect API keys or credentials for external services to trigger authentication errors.
- Automated:
- Integration Tests with Mocked Third Parties: Write tests that simulate specific third-party API error responses and assert that your application handles them gracefully, displaying appropriate user messages and logging relevant details.
- Contract Testing: For critical integrations, implement contract tests (e.g., Pact) to ensure your application's expectations of the third-party API's error responses align with the actual provider's contract.
How to fix and prevent:
- Dedicated Third-Party Error Handling: Map known third-party error codes and messages to your application's user-friendly error messages.
- Fallback Mechanisms: Implement fallback logic if a non-critical third-party service fails (e.g., display a cached version, disable the feature gracefully).
- Circuit Breakers for Third Parties: Protect your application from cascading failures due to persistently failing external services.
- Logging External Errors: Log the full details of third-party errors (status codes, response bodies) for debugging and monitoring, but sanitize them for user display.
Test Matrix for Error Handling Coverage
A structured approach is vital. This table provides a high-level test matrix to ensure comprehensive error handling coverage.
| Error Type Category | Specific Bug Pattern | User Impact | Detection Strategy (Manual) | Detection Strategy (Automated) | Prevention/Fix |
|---|---|---|---|---|---|
| User Input Errors | Invalid Data Format | Form submission fails without feedback or with generic message. | Enter malformed data (e.g., text in number field, incorrect email). | UI validation tests, API schema validation tests. | Client-side & server-side validation, clear inline error messages. |
| Missing Required Field | Form submits silently or with unhelpful message. | Submit form with empty required fields. | UI validation tests, API schema validation tests. | required attribute, specific inline error messages. | |
| Network Errors | Offline/Disconnected | App hangs, crashes, or shows no state change. | Toggle Wi-Fi/data off mid-action, simulate network loss. | Network condition simulation in E2E tests, catch blocks for network exceptions. | Global network error handler, offline-first design, retry logic. |
| Slow Network/Timeout | App hangs, loading spinner never resolves. | Throttle network (DevTools, proxy). | Performance tests with network throttling, configurable timeouts. | Timeouts for all network requests, user-friendly timeout message. | |
| Backend Errors | Internal Server Error (5xx) | Silent failure, app crash, generic "error occurred." | Proxy: change API response to 500/503. | API tests asserting UI feedback on 5xx, E2E tests with mocked 5xx. | Centralized error handling, graceful degradation, monitoring. |
| Bad Request (4xx) | Incorrect parsing of specific 4xx codes. | Proxy: change API response to 400/401/403. | API tests asserting specific 4xx messages, E2E tests with mocked 4xx. | Map 4xx codes to specific user messages, proper authentication/authorization checks. | |
| External Service Errors | Third-Party API Failure | Feature disabled silently, wrong data displayed. | Mock third-party API to return errors/timeouts. | Integration tests with mocked third-party errors, circuit breakers. | Fallback mechanisms, dedicated error mapping for external services. |
| Application Logic Errors | Unhandled Exception | App crash, ANR. | Edge case inputs, rapid interactions, resource exhaustion. |
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free