Skip to content

Websocket Error Handling

DodaTech 6 min read

title: "WebSocket Error Handling" description: "Learn how to handle WebSocket errors gracefully including connection failures, protocol errors, frame parsing errors, and server-side exceptions." weight: 24 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]


Error handling in WebSocket applications covers connection failures, protocol violations, message parsing errors, and server-side exceptions. Proper error handling prevents crashes and provides meaningful feedback.

## What You'll Learn

- Common WebSocket error types
- Server-side error handling
- Client-side error recovery
- Error propagation patterns
- Logging and monitoring errors

## Why It Matters

WebSocket connections are long-lived and subject to many failure modes. Without proper error handling, failures cascade, connections leak, and users experience silent data loss.

## Real-World Use

A real-time stock trading platform categorizes WebSocket errors into network (reconnect), protocol (close connection), business (notify user), and system (alert operations). Each category triggers an appropriate response.

## Flow Chart

```mermaid
flowchart TD
    A[WebSocket Error] --> B{Error Type}
    B --> C[Connection Error]
    B --> D[Protocol Error]
    B --> E[Application Error]
    B --> F[Timeout Error]
    C --> G[Reconnect]
    D --> H[Close Connection]
    E --> I[Notify User]
    F --> J[Retry or Reconnect]

Code Examples

Example 1: Comprehensive Server Error Handling

const WebSocket = require('ws');

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws, req) => {
  console.log('New connection from:', req.socket.remoteAddress);

  // Handle errors at the connection level
  ws.on('error', (error) => {
    console.error('WebSocket error:', error.message);
    // Log error details
    logError({
      type: 'websocket_error',
      message: error.message,
      code: error.code,
      remoteAddress: req.socket.remoteAddress,
      timestamp: new Date().toISOString(),
    });
  });

  // Handle messages with validation
  ws.on('message', (data) => {
    try {
      const parsed = JSON.parse(data);
      
      if (!parsed.type || typeof parsed.type !== 'string') {
        sendError(ws, 400, 'Invalid message format');
        return;
      }

      handleMessage(ws, parsed);
    } catch (parseError) {
      sendError(ws, 400, 'Invalid JSON payload');
      logError({
        type: 'parse_error',
        raw: data.toString().substring(0, 200),
        error: parseError.message,
      });
    }
  });

  // Handle unexpected disconnection
  ws.on('close', (code, reason) => {
    const closeReason = reason?.toString() || 'No reason provided';
    console.log(`Connection closed: ${code} - ${closeReason}`);
    
    cleanupUserResources(ws);
  });
});

function sendError(ws, code, message) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({
      type: 'error',
      code,
      message,
      timestamp: Date.now(),
    }));
  }
}

function handleMessage(ws, message) {
  try {
    switch (message.type) {
      case 'subscribe':
        if (!message.channel) {
          sendError(ws, 400, 'channel is required for subscribe');
          return;
        }
        subscribeToChannel(ws, message.channel);
        break;

      case 'publish':
        if (!message.topic || !message.payload) {
          sendError(ws, 400, 'topic and payload are required');
          return;
        }
        publishMessage(ws, message);
        break;

      default:
        sendError(ws, 400, `Unknown message type: ${message.type}`);
    }
  } catch (handlerError) {
    console.error('Handler error:', handlerError);
    sendError(ws, 500, 'Internal server error');
    logError({
      type: 'handler_error',
      message: message,
      error: handlerError.message,
      stack: handlerError.stack,
    });
  }
}

server.on('error', (error) => {
  console.error('Server error:', error);
  // Server-level error handling (e.g., port conflict)
});

Expected output: Server handles parsing errors, validation errors, business logic errors, and unexpected exceptions with appropriate responses and logging.

Example 2: Client Error Recovery

class ResilientWebSocketClient {
  constructor(url) {
    this.url = url;
    this.errorCount = 0;
    this.maxErrors = 5;
    this.connect();
  }

  connect() {
    try {
      this.ws = new WebSocket(this.url);
    } catch (error) {
      console.error('Failed to create WebSocket:', error);
      this.scheduleReconnect();
      return;
    }

    this.ws.onopen = () => {
      this.errorCount = 0;
      console.log('Connected');
    };

    this.ws.onerror = (event) => {
      this.errorCount++;
      console.error('WebSocket error:', event.message || 'Unknown error');
      
      // Categorize errors
      const errorInfo = this.categorizeError(event);
      
      switch (errorInfo.category) {
        case 'network':
          // Network errors are recoverable
          console.log('Network error, will reconnect');
          break;
        case 'protocol':
          // Protocol errors indicate server incompatibility
          console.error('Protocol error, closing permanently');
          this.ws.close(1002, 'Protocol error');
          break;
        case 'timeout':
          // Timeout errors may recover
          console.log('Timeout, retrying...');
          break;
        default:
          console.warn('Unknown error category');
      }
    };

    this.ws.onclose = (event) => {
      const closeInfo = {
        code: event.code,
        reason: event.reason,
        wasClean: event.wasClean,
      };
      
      console.log('Connection closed:', closeInfo);
      
      if (!event.wasClean && this.errorCount < this.maxErrors) {
        this.scheduleReconnect();
      }
    };
  }

  categorizeError(event) {
    if (event.message?.includes('timeout') || event.message?.includes('timed out')) {
      return { category: 'timeout' };
    }
    if (event.message?.includes('handshake') || event.message?.includes('upgrade')) {
      return { category: 'protocol' };
    }
    if (event.message?.includes('ENETUNREACH') || event.message?.includes('ECONNREFUSED')) {
      return { category: 'network' };
    }
    return { category: 'unknown' };
  }

  scheduleReconnect() {
    const delay = Math.min(1000 * Math.pow(2, this.errorCount), 30000);
    console.log(`Reconnecting in ${delay}ms`);
    setTimeout(() => this.connect(), delay);
  }
}

Expected output: Client categorizes errors and responds appropriately: reconnecting for network errors, closing for protocol errors, and tracking error counts.

Example 3: Error Handling with Close Codes

const WebSocket = require('ws');

// Standard WebSocket close codes
const CLOSE_CODES = {
  NORMAL_CLOSURE: 1000,
  GOING_AWAY: 1001,
  PROTOCOL_ERROR: 1002,
  UNSUPPORTED_DATA: 1003,
  NO_STATUS_RECEIVED: 1005,
  ABNORMAL_CLOSURE: 1006,
  INVALID_PAYLOAD: 1007,
  POLICY_VIOLATION: 1008,
  MESSAGE_TOO_BIG: 1009,
  MANDATORY_EXTENSION: 1010,
  INTERNAL_ERROR: 1011,
  SERVICE_RESTART: 1012,
  TRY_AGAIN_LATER: 1013,
};

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws) => {
  ws.on('message', (data) => {
    try {
      const message = JSON.parse(data);

      // Validation with proper close codes
      if (message.text && message.text.length > 10000) {
        ws.close(CLOSE_CODES.MESSAGE_TOO_BIG, 'Message exceeds 10KB limit');
        return;
      }

      if (message.type === 'admin' && !isAdmin(message.token)) {
        ws.close(CLOSE_CODES.POLICY_VIOLATION, 'Admin access required');
        return;
      }

      processMessage(ws, message);
    } catch (error) {
      ws.close(CLOSE_CODES.INVALID_PAYLOAD, 'Invalid JSON');
    }
  });
});

// Client handling close codes
const client = new WebSocket('wss://server.example.com/ws');

client.onclose = (event) => {
  switch (event.code) {
    case 1000:
      console.log('Normal closure');
      break;
    case 1001:
      console.log('Server going away, will reconnect');
      break;
    case 1008:
      console.log('Policy violation:', event.reason);
      showAuthError(event.reason);
      break;
    case 1009:
      console.log('Message too large, reducing payload size');
      reduceChunkSize();
      break;
    case 1011:
      console.log('Server error, retrying later');
      scheduleReconnect(5000);
      break;
    case 1012:
    case 1013:
      console.log('Server restarting, retrying...');
      scheduleReconnect(1000);
      break;
    default:
      if (event.code === 1006) {
        console.log('Abnormal closure, reconnecting');
        reconnect();
      }
  }
};

Expected output: Server uses appropriate close codes for different error scenarios, and client handles each code with specific recovery logic.

Common Mistakes

Mistake Explanation
Not handling onerror events Unhandled errors cause uncaught exceptions and crash the process
Using generic error messages Include error codes and categories so clients can respond appropriately
Not logging error context Log IP addresses, user IDs, and message types with errors for debugging
Leaking sensitive info in errors Do not expose stack traces, SQL queries, or internal paths in error messages
Setting error-based reconnect loops Track error counts and use backoff to prevent infinite reconnect loops

Practice Questions

  1. What are the standard WebSocket close codes and their meanings?
  2. How do you handle JSON parsing errors in WebSocket messages?
  3. What is the difference between onerror and onclose events?
  4. How do you implement graceful error recovery on the client?
  5. How do you log WebSocket errors effectively?

Challenge

Build a WebSocket server with comprehensive error handling that categorizes all errors, logs them with structured context, reports metrics to a monitoring system, and sends appropriate close codes with meaningful reasons.

FAQ

What causes WebSocket errors?

Common causes: network interruptions, server crashes, protocol mismatches, message size limits, authentication failures, and proxy timeouts.

Can I recover from all WebSocket errors?

No, some errors (protocol error, unsupported data) indicate fundamental issues that require reconnection or code changes.

How do I handle errors in Socket.IO?

Socket.IO provides connect_error, reconnect_error, and error events. Use middleware for centralized error handling.

What is the difference between error codes and close codes?

Error codes are application-level (in message payload), close codes are protocol-level (in close frame). Use both for comprehensive error handling.

Should I retry on authentication errors?

No, authentication errors indicate invalid credentials. Retrying without fixing credentials will keep failing.

How do I debug WebSocket errors?

Use browser DevTools Network tab, enable WebSocket frame logging, check server logs, and use packet sniffers like Wireshark.

Mini Project

Build a WebSocket error monitoring dashboard. The server sends structured error events to a monitoring endpoint. The dashboard displays error rates, categories, close code distributions, and per-client error histories. Include alerting for specific error thresholds.

What's Next

Learn about WebSocket security best practices

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro