mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Invalid BulkOperation, Batch cannot be empty

Error message

Invalid BulkOperation, Batch cannot be empty

What it means

Thrown as MongoInvalidArgumentError by BulkOperationBase.execute() (src/bulk/common.ts:1201) when execute() is called on a bulk operation to which no operations were added. The driver has no batch to send, so it refuses to issue an empty wire command rather than round-tripping a no-op.

Solutions

  1. Guard the execute() call: only call it when you have at least one operation (`if (myOps.length) await coll.bulkWrite(myOps)`).
  2. Skip the whole bulk block when the input list is empty.
  3. If using the builder API, track whether you added any ops before invoking execute().

Example fix

// before
const bulk = coll.initializeUnorderedBulkOp();
for (const id of []) { // empty input
  bulk.find({ _id: id }).deleteOne();
}
await bulk.execute(); // throws: Batch cannot be empty

// after
const ids: unknown[] = [];
if (ids.length > 0) {
  const bulk = coll.initializeUnorderedBulkOp();
  for (const id of ids) bulk.find({ _id: id }).deleteOne();
  await bulk.execute();
}
Defensive patterns

Strategy: validation

Validate before calling

if (myOps.length === 0) {
  // nothing to do — skip execute entirely
  return;
}
const bulk = coll.initializeUnorderedBulkOp();
for (const op of myOps) /* add ops */;
await bulk.execute();

Type guard

function hasAny<T>(arr: readonly T[]): arr is [T, ...T[]] {
  return arr.length > 0;
}

if (hasAny(myOps)) {
  // build and execute the bulk
}

Try / catch

try {
  await bulk.execute();
} catch (e) {
  if (e instanceof MongoInvalidArgumentError && /Batch cannot be empty/.test(e.message)) {
    // no-op: nothing to write; swallow or log
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `await coll.initializeOrderedBulkOp().execute()` (or the unordered variant) without any preceding insert/find/update/delete calls. Guarded by `if (this.s.batches.length === 0)` at src/bulk/common.ts:1200.

Common situations: A loop that builds ops conditionally produced zero ops, a code path that skips all operations due to empty input, or calling execute() defensively at the end of a function without checking that ops were added.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/5a6d951a644e7d7e. Report an issue: GitHub.

Appendix: source

Thrown at src/bulk/common.ts:1201

      throw new MongoBatchReExecutionError();
    }

    const writeConcern = WriteConcern.fromOptions(options);
    if (writeConcern) {
      this.s.writeConcern = writeConcern;
    }

    // If we have current batch
    if (this.isOrdered) {
      if (this.s.currentBatch) this.s.batches.push(this.s.currentBatch);
    } else {
      if (this.s.currentInsertBatch) this.s.batches.push(this.s.currentInsertBatch);
      if (this.s.currentUpdateBatch) this.s.batches.push(this.s.currentUpdateBatch);
      if (this.s.currentRemoveBatch) this.s.batches.push(this.s.currentRemoveBatch);
    }
    // If we have no operations in the bulk raise an error
    if (this.s.batches.length === 0) {
      throw new MongoInvalidArgumentError('Invalid BulkOperation, Batch cannot be empty');
    }

    this.s.executed = true;
    const finalOptions = resolveOptions(this.collection, { ...this.s.options, ...options });

    // if there is no timeoutContext provided, create a timeoutContext and use it for
    // all batches in the bulk operation
    finalOptions.timeoutContext ??= TimeoutContext.create({
      session: finalOptions.session,
      timeoutMS: finalOptions.timeoutMS,
      serverSelectionTimeoutMS: this.collection.client.s.options.serverSelectionTimeoutMS,
      waitQueueTimeoutMS: this.collection.client.s.options.waitQueueTimeoutMS
    });

    if (finalOptions.session == null) {
      // if there is not an explicit session provided to `execute()`, create
      // an implicit session and use that for all batches in the bulk operation
      return await this.collection.client.withSession({ explicit: false }, async session => {

View on GitHub (pinned to dce7939f86)