← back

Error
Handling

FILE  43_error_handling
TOPIC  Error Codes · BulkWrite · Connection Errors · Retry Logic · Timeouts · Validation
LEVEL  Intermediate
01
Error Code Reference
Common MongoDB error codes and their meanings
codes
CodeNameCauseRetryable?
11000DuplicateKeyUnique index violationNo — fix data
11001DuplicateKeyOnUpdateUnique violation on updateNo — fix data
112WriteConflictTwo operations modified same doc concurrently (WiredTiger)Yes — retry
121DocumentValidationFailureDocument fails JSON Schema validationNo — fix document
251NoSuchTransactionTransaction expired or was aborted by serverYes — retry whole txn
256TransactionExceededLifetimeTransaction exceeded 60s lifetime limitRedesign needed
50MaxTimeMSExpiredQuery exceeded maxTimeMS timeoutMaybe — check query plan
91ShutdownInProgressmongod is shutting downYes — after restart
189PrimarySteppedDownPrimary stepped down during electionYes — after election
9001SocketExceptionNetwork connection droppedYes
10107NotPrimaryNoSecondaryOkNot primary, not a readable secondaryYes — connect to primary
13435NotPrimaryOrSecondaryMember in RECOVERING or STARTUP stateYes
13436InterruptedAtShutdownOp interrupted during mongod shutdownYes
// Accessing error code in Node.js driver
try {
  await db.collection("users").insertOne({ _id: "duplicate", name: "Alice" })
} catch (err) {
  console.log(err.code)           // 11000 for duplicate key
  console.log(err.name)           // "MongoServerError"
  console.log(err.message)        // "E11000 duplicate key error collection: ..."
  console.log(err.errorLabels)    // ["TransientTransactionError"] for retryable

  if (err.code === 11000) {
    // Extract which field/value caused the violation:
    console.log(err.keyPattern)   // { email: 1 }
    console.log(err.keyValue)     // { email: "alice@example.com" }
  }
}
02
Write Errors
WriteError vs WriteConcernError — two distinct failure types
write

MongoDB distinguishes two types of write failure: a WriteError (the write was attempted but failed at the storage level — e.g., duplicate key) and a WriteConcernError (the write succeeded locally but failed to replicate to the required number of nodes within the timeout).

// WriteError — write rejected by the server
try {
  await db.collection("users").updateOne(
    { _id: "user1" },
    { $set: { email: "already@taken.com" } }
  )
} catch (err) {
  if (err.name === "MongoServerError") {
    // Logical error — fix the data or query
    console.log("Write rejected:", err.code, err.message)
  }
}

// WriteConcernError — replication acknowledgment failed
// Raised when wtimeout expires before w nodes acknowledge
try {
  await db.collection("orders").insertOne(newOrder, {
    writeConcern: { w: "majority", wtimeout: 2000 }
  })
} catch (err) {
  if (err.name === "MongoWriteConcernError") {
    // The write MAY have been committed — uncertain state
    // Do NOT assume it failed and retry blindly
    // Check if the document now exists:
    const exists = await db.collection("orders").findOne({ _id: newOrder._id })
    if (!exists) await db.collection("orders").insertOne(newOrder)
    // Or: use idempotent writes with a unique insertionId
  }
}

// WriteConcernError specific fields:
// err.result.writeConcernError.code    — error code (64 = WriteConcernTimeout)
// err.result.writeConcernError.errmsg  — human readable message
// err.result.n                         — documents affected before error
DANGER
A WriteConcernError (timeout) does NOT mean the write was rolled back. The write may have been applied on the primary and not yet replicated. Never retry unconditionally on a WriteConcernError without first checking if the document exists. Use idempotent writes with a client-generated idempotency key to safely retry.
03
BulkWrite Errors
Partial failure handling in bulk operations
bulk
// bulkWrite with ordered: true (default) — stops at first error
try {
  const result = await db.collection("users").bulkWrite([
    { insertOne: { document: { _id: 1, name: "Alice" } } },
    { insertOne: { document: { _id: 1, name: "Duplicate" } } },  // FAILS here
    { insertOne: { document: { _id: 3, name: "Carol" } } }       // never reached
  ], { ordered: true })
} catch (err) {
  if (err.name === "MongoBulkWriteError") {
    console.log("Inserted before error:", err.result.nInserted)   // 1
    console.log("Write errors:", err.writeErrors)
    // err.writeErrors[0].index  — which operation failed (0-based)
    // err.writeErrors[0].code   — 11000 = duplicate key
    // err.writeErrors[0].errmsg — error message
  }
}

// bulkWrite with ordered: false — continues on error, collects all errors
try {
  const result = await db.collection("products").bulkWrite([
    { updateOne: { filter: { _id: "P1" }, update: { $inc: { stock: -1 } } } },
    { updateOne: { filter: { _id: "P2" }, update: { $inc: { stock: -5 } } } },
    { updateOne: { filter: { _id: "P3" }, update: { $inc: { stock: -2 } } } }
  ], { ordered: false })

  console.log("Matched:", result.matchedCount)
  console.log("Modified:", result.modifiedCount)
} catch (err) {
  if (err.name === "MongoBulkWriteError") {
    // Some ops succeeded — check err.result for partial success
    console.log("Successful writes:", err.result.nModified)
    console.log("Failed writes:", err.writeErrors.length)
    err.writeErrors.forEach(e => {
      console.log(`Op ${e.index} failed: code=${e.code} msg=${e.errmsg}`)
    })
  }
}

ordered: true vs ordered: false

ModeOn ErrorPerformanceUse When
ordered: trueStops immediately; remaining ops skippedSequentialOperations are dependent on each other
ordered: falseContinues; collects ALL errorsParallel (faster)Independent operations; partial success acceptable
04
Connection Errors
Network failures, server selection, and connection pool
connection
// MongoNetworkError — connection dropped mid-operation
// MongoServerSelectionError — cannot reach any suitable server
// MongoTopologyClosedError — client has been closed

// Connection string timeouts to configure:
const client = new MongoClient(uri, {
  serverSelectionTimeoutMS: 5000,  // How long to wait to find a server (default 30s)
  connectTimeoutMS:         10000, // How long to wait for TCP connect (default 10s)
  socketTimeoutMS:          0,     // 0 = no socket read/write timeout (not recommended)
  maxPoolSize:              50,    // Max connections in pool per mongos/mongod
  minPoolSize:              5,     // Maintain at least 5 idle connections
  waitQueueTimeoutMS:       5000,  // Max wait for a pool connection (default 0 = infinite)
  heartbeatFrequencyMS:     10000, // How often to check server health (default 10s)
})

// Handle connection errors:
try {
  await someMongoOperation()
} catch (err) {
  if (err.name === "MongoNetworkError") {
    // Network issue — retry after brief delay
    // Driver auto-reconnects — may just need to retry the operation
    await sleep(100)
    return retry(someMongoOperation)
  }
  if (err.name === "MongoServerSelectionError") {
    // Cannot find a primary — cluster may be in election
    // Wait and retry (election takes ~10-12s)
    await sleep(15000)
    return retry(someMongoOperation)
  }
  throw err  // unknown error — escalate
}

// Connection pool monitoring (Node.js driver events)
client.on("connectionPoolCreated",  e => metrics.increment("mongo.pool.created"))
client.on("connectionCreated",      e => metrics.increment("mongo.conn.created"))
client.on("connectionClosed",       e => metrics.increment("mongo.conn.closed"))
client.on("connectionCheckOutFailed", e => {
  metrics.increment("mongo.pool.checkout_failed")
  // reason: e.reason  — "timeout" means pool exhausted
})
05
Timeout Errors
maxTimeMS, wtimeout, serverSelectionTimeoutMS
timeouts
// maxTimeMS — per-operation query timeout (aborts on server side)
db.orders.find({ status: "pending" })
  .maxTimeMS(5000)     // abort if query takes > 5 seconds
// Driver throws MongoOperationTimeoutError (code 50: MaxTimeMSExpired)
// The operation is killed server-side — no partial result

await db.collection("orders").aggregate(pipeline, { maxTimeMS: 10000 })
await db.collection("orders").findOne(filter, { maxTimeMS: 2000 })

// Global default maxTimeMS on client:
const client = new MongoClient(uri, {
  timeoutMS: 5000    // MongoDB 8.0+ unified client timeout (CSOT)
})

// wtimeout — write concern acknowledgment timeout
// Code 64: WriteConcernTimeout (write may have completed!)
await db.collection("orders").insertOne(doc, {
  writeConcern: { w: "majority", wtimeout: 3000 }
})

// Diagnose MaxTimeMSExpired: identify the slow query using explain
// Step 1: Get the query from the error log
// Step 2: Run explain("executionStats") on it
// Step 3: Check planSummary — COLLSCAN = missing index
// Step 4: Create the appropriate index
// Step 5: Optionally increase maxTimeMS to buy time while index is building

// Background index build won't block queries but won't speed them up until complete:
db.orders.createIndex({ status: 1, createdAt: -1 }, { background: true })
// In MongoDB 4.2+, all index builds are non-blocking (background: true is default)
06
Validation Errors
Schema validation failures — code 121
validation
// Add JSON Schema validation to a collection
db.createCollection("orders", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["customerId", "total", "status"],
      properties: {
        customerId: { bsonType: "string",  description: "must be a string and required" },
        total:      { bsonType: "double",  minimum: 0,  description: "non-negative number" },
        status:     {
          bsonType: "string",
          enum: ["pending", "confirmed", "shipped", "completed", "cancelled"]
        },
        lineItems:  {
          bsonType: "array",
          minItems: 1,
          items: {
            bsonType: "object",
            required: ["productId", "qty"],
            properties: {
              productId: { bsonType: "string" },
              qty:       { bsonType: "int", minimum: 1 }
            }
          }
        }
      }
    }
  },
  validationAction: "error",    // "error" = reject | "warn" = log only
  validationLevel:  "strict"    // "strict" = all writes | "moderate" = only new docs
})

// Handle validation error:
try {
  await db.collection("orders").insertOne({ total: -10, status: "pending" })
} catch (err) {
  if (err.code === 121) {
    // err.message contains which validation rule failed
    console.log("Validation failed:", err.message)
    // Document validation failure. Document failed validation... 
    // Failing element { ... } ← shows which field failed
  }
}

// Update validation rules on existing collection
db.runCommand({
  collMod: "orders",
  validator: {
    $jsonSchema: {
      /* updated schema */
    }
  },
  validationLevel: "moderate"   // only validate new/fully-replaced docs, not updates
})

// Bypass validation (requires bypassDocumentValidation privilege)
await db.collection("orders").insertOne(doc, {
  bypassDocumentValidation: true   // for migrations/data imports only
})
07
Retry Patterns
Which errors to retry, how to retry safely
retry

Retryable vs Non-Retryable Errors

Error TypeRetryable?Condition
MongoNetworkErrorYeshasErrorLabel("RetryableWriteError")
WriteConflict (112)YesAlways — transient WiredTiger conflict
PrimarySteppedDown (189)YesWait for election (~10–15s)
NotPrimary (10107)YesReconnect to new primary
DuplicateKey (11000)NoFix data or use upsert
DocumentValidationFailure (121)NoFix document structure
MaxTimeMSExpired (50)MaybeOnly if query was optimizable
WriteConcernTimeout (64)CarefullyCheck existence first; idempotent only
// Retryable writes — enabled by default in modern drivers
// Automatically retries once on network errors for insert/update/delete
const client = new MongoClient(uri, { retryWrites: true })  // default true

// Retryable reads — automatically retries find/aggregate on network error
const client = new MongoClient(uri, { retryReads: true })   // default true

// Custom retry with exponential backoff for application-level retries
async function retryWithBackoff(fn, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (err) {
      const isRetryable =
        err.hasErrorLabel?.("RetryableWriteError") ||
        err.code === 112 ||   // WriteConflict
        err.code === 189 ||   // PrimarySteppedDown
        err.name === "MongoNetworkError"

      if (!isRetryable || attempt === maxAttempts) throw err

      const delay = Math.min(100 * Math.pow(2, attempt), 3000)  // cap at 3s
      console.warn(`Retry ${attempt}/${maxAttempts} after ${delay}ms: ${err.message}`)
      await new Promise(res => setTimeout(res, delay))
    }
  }
}

// Usage:
await retryWithBackoff(() =>
  db.collection("orders").updateOne(
    { _id: orderId },
    { $set: { status: "confirmed" } }
  )
)
TIP
Design write operations to be idempotent when possible — applying the same operation multiple times produces the same result. $set is idempotent (setting the same value twice is safe); $inc is not (incrementing twice doubles the effect). For non-idempotent operations where you need to safely retry, include an idempotency key in the document and use upsert with $setOnInsert.