How to fix MongoExpiredSessionError: Cannot use a session that has ended in MongoDB

MongoDBINTERMEDIATEMEDIUMUpdated Dec 22, 2025· 180 views

This error occurs when attempting to use a MongoDB ClientSession after it has been explicitly ended or has expired. Sessions must be active to perform database operations within transactions or to maintain operation consistency.

Quick fix

Fixes MongoExpiredSessionError

// BAD: Per-request connection
app.post('/users', async (req, res) => {
  const client = await MongoClient.connect(uri);
  const db = client.db('myapp');
  await db.collection('users').insertMany(users);
  await client.close(); // Closes before insertMany completes!
  res.send('OK');
});

// GOOD: Persistent connection
let client;
let db;

async function startApp() {
  client = await MongoClient.connect(uri);
  db = client.db('myapp');
  app.listen(3000);
}

app.post('/users', async (req, res) => {
  await db.collection('users').insertMany(users);
  res.send('OK');
});

startApp();
  1. Establish persistent database connection at application startup
  2. Always await async operations before closing sessions
  3. Use session.withTransaction() for transaction operations
  4. Increase session timeout if operations are legitimately long-running
mongodb — error
// BAD: Per-request connection
app.post('/users', async (req, res) => {
const client = await MongoClient.connect(uri);
const db = client.db('myapp');
await db.collection('users').insertMany(users);
await client.close(); // Closes before insertMany completes!
res.send('OK');
});
// GOOD: Persistent connection
let client;
let db;
async function startApp() {
client = await MongoClient.connect(uri);
db = client.db('myapp');
app.listen(3000);
}
app.post('/users', async (req, res) => {
await db.collection('users').insertMany(users);
res.send('OK');
});
startApp();
MongoExpiredSessionError: Cannot use a session that has ended

What this error means

The MongoExpiredSessionError indicates that your application is trying to execute database operations using a ClientSession that is no longer valid. MongoDB sessions are used to enable causally consistent operations, retryable writes, and multi-document ACID transactions. Once a session is ended—either by calling endSession() explicitly, by the session timing out due to inactivity, or by the database connection being closed prematurely—any subsequent attempt to use that session will trigger this error. Sessions in MongoDB have a lifecycle and are meant to be short-lived within the context of specific operations or transactions. The driver tracks the session state, and once a session transitions to an "ended" state, the underlying resources are released and the session object becomes unusable. This error typically surfaces when there's a mismatch between when your code expects the session to be active and when it has actually been terminated. Common scenarios include closing database connections before asynchronous operations complete, reusing session objects across multiple sequential operations without proper lifecycle management, or having concurrent requests share the same connection/session in a way that causes one request's cleanup to invalidate another's active session.

Typical symptoms

  • Database operations fail with "Cannot use a session that has ended" error message
  • Transactions fail to commit or abort with session-related errors
  • Error occurs intermittently, especially under concurrent load
  • Operations like insertMany(), find(), or cursor.toArray() throw MongoExpiredSessionError
  • Error appears when application receives multiple concurrent requests
  • Problems manifest after connection close or restart operations

Common causes

  1. Closing the database connection before async operations complete—close() executes faster than pending insertMany() or other operations
  2. Calling endSession() explicitly on a session object and then attempting to reuse it
  3. Opening per-request database connections that close while other requests are still processing
  4. Session timeout due to inactivity exceeding the configured session idle time
  5. Using the same session object across concurrent operations without proper synchronization
  6. Application shutdown or restart terminating sessions while operations are in progress
  7. Not awaiting async operations properly, causing session cleanup to run prematurely
  8. Using session objects outside their intended scope or after transaction completion

How to fix "MongoExpiredSessionError: Cannot use a session that has ended"

1Establish persistent database connection at application startup

Instead of opening and closing connections per request, create a single persistent connection when your application starts. This ensures sessions remain valid throughout operation execution.

javascript
// BAD: Per-request connection
app.post('/users', async (req, res) => {
  const client = await MongoClient.connect(uri);
  const db = client.db('myapp');
  await db.collection('users').insertMany(users);
  await client.close(); // Closes before insertMany completes!
  res.send('OK');
});

// GOOD: Persistent connection
let client;
let db;

async function startApp() {
  client = await MongoClient.connect(uri);
  db = client.db('myapp');
  app.listen(3000);
}

app.post('/users', async (req, res) => {
  await db.collection('users').insertMany(users);
  res.send('OK');
});

startApp();

This pattern ensures the connection and its sessions remain active for the lifetime of your application.

2Always await async operations before closing sessions

Ensure all database operations complete before calling endSession() or closing connections. Use async/await consistently.

javascript
// BAD: Session closed before operation completes
const session = client.startSession();
collection.insertMany(docs, { session });
session.endSession(); // Called immediately, operation still running!

// GOOD: Await operation completion
const session = client.startSession();
try {
  await collection.insertMany(docs, { session });
} finally {
  await session.endSession();
}

The finally block ensures the session is properly cleaned up even if errors occur, but only after operations complete.

3Use session.withTransaction() for transaction operations

MongoDB's withTransaction() helper automatically manages session lifecycle and handles retries for transient errors.

javascript
// Using withTransaction helper
const session = client.startSession();
try {
  await session.withTransaction(async () => {
    await collection1.insertOne(doc1, { session });
    await collection2.updateOne(filter, update, { session });
    // Session automatically managed, no manual endSession needed
  });
} finally {
  await session.endSession();
}

// For Mongoose users
await mongoose.connection.transaction(async (session) => {
  await User.create([{ name: 'John' }], { session });
  await Order.create([{ userId: '123' }], { session });
  // Mongoose handles session lifecycle
});

This approach eliminates manual transaction management errors and ensures proper cleanup.

4Increase session timeout if operations are legitimately long-running

For operations that genuinely require more time, configure longer session timeouts in your connection options.

javascript
const client = new MongoClient(uri, {
  maxIdleTimeMS: 120000, // 2 minutes instead of default 30 seconds
  serverSelectionTimeoutMS: 30000,
});

However, prefer optimizing your queries over increasing timeouts. Use maxTimeMS for individual operations rather than relying on socket timeouts:

javascript
// Better: Set operation-level timeout
await collection.find(query)
  .maxTimeMS(10000) // 10 seconds for this query
  .toArray();

This allows MongoDB to cancel slow operations server-side rather than waiting for socket timeouts.

5Verify proper error handling and propagation

Ensure errors in async operations aren't silently caught, which can cause sessions to be cleaned up while retries are attempted.

javascript
// BAD: Silent error handling breaks retry logic
session.withTransaction(async () => {
  try {
    await collection.insertOne(doc, { session });
  } catch (err) {
    console.log('Error occurred'); // Swallowed!
    // Session state now inconsistent
  }
});

// GOOD: Let errors propagate
session.withTransaction(async () => {
  await collection.insertOne(doc, { session });
  // Errors propagate to withTransaction retry logic
});

If you must handle errors, ensure they're re-thrown so the session management logic can respond appropriately.

6Check for concurrent session usage in multi-request scenarios

Ensure each request gets its own session and doesn't share sessions across concurrent operations.

javascript
// BAD: Shared global session
let globalSession = client.startSession();
app.post('/data', async (req, res) => {
  await collection.insertOne(doc, { session: globalSession });
});

// GOOD: Per-operation session
app.post('/data', async (req, res) => {
  const session = client.startSession();
  try {
    await collection.insertOne(doc, { session });
  } finally {
    await session.endSession();
  }
});

For most use cases, let the driver manage sessions implicitly—explicit sessions are only needed for multi-document transactions or causal consistency requirements.

Advanced notes

Session Lifecycle Best Practices: MongoDB sessions are designed for short-lived operations. The default session idle timeout is 30 minutes on the server side, but client drivers may enforce shorter timeouts. Always match your session usage patterns to their intended scope—sessions for transactions should span only the transaction duration, not the entire request lifecycle.

Connection Pooling Implications: When using connection pooling (the default in most drivers), sessions are tied to specific connections from the pool. Abruptly closing connections or having pool exhaustion can cause sessions to become invalidated. Configure appropriate pool sizes (minPoolSize/maxPoolSize) based on your concurrency needs.

Mongoose-Specific Considerations: Mongoose adds its own session management layer. When using Mongoose's Connection#transaction() or Session#withTransaction(), the library handles session creation and cleanup. Mixing manual session management with Mongoose's helpers can lead to lifecycle conflicts. Stick to one pattern consistently.

Debugging Tips: Enable MongoDB driver debug logging to track session creation and destruction. For the Node.js driver, set the environment variable DEBUG=mongo:* or use driver-level logging options. This visibility helps identify where sessions are being ended prematurely or reused incorrectly.

Serverless/Lambda Environments: In serverless functions where connections may persist across invocations, ensure you're not accidentally reusing sessions from previous function executions. Either create fresh sessions per invocation or use explicit session lifecycle management with proper cleanup in finally blocks.

Retryable Writes: MongoDB's retryable writes feature (enabled by default in driver versions 4.2+) may retry operations automatically. If a session expires during a retry, you'll see this error. The solution is the same: ensure sessions remain active for the full operation duration, or let the driver manage sessions implicitly for non-transactional operations.

Frequently asked questions

What does "MongoExpiredSessionError" mean?
The MongoExpiredSessionError indicates that your application is trying to execute database operations using a ClientSession that is no longer valid. MongoDB sessions are used to enable causally consistent operations, retryable writes, and multi-document ACID transactions. Once a session is ended—either by calling endSession() explicitly, by the session timing out due to inactivity, or by the database connection being closed prematurely—any subsequent attempt to use that session will trigger this error.
How do I fix "MongoExpiredSessionError"?
This error occurs when attempting to use a MongoDB ClientSession after it has been explicitly ended or has expired. Sessions must be active to perform database operations within transactions or to maintain operation consistency.
What causes "MongoExpiredSessionError"?
Closing the database connection before async operations complete—close() executes faster than pending insertMany() or other operations Calling endSession() explicitly on a session object and then attempting to reuse it Opening per-request database connections that close while other requests are still processing
Is the "MongoExpiredSessionError" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading