mybatis/mybatis-3 · error · SqlSessionException

Error: Cannot close. No managed session is started.

Error message

Error:  Cannot close.  No managed session is started.

What it means

SqlSessionManager.close() throws SqlSessionException 'Cannot close. No managed session is started.' when the thread-local is already empty. Unlike the operation methods, close() also removes the thread-local in a finally, so double-close is what usually triggers this: the first close() detaches the session, the second finds null and throws instead of being idempotent.

Source

Thrown at src/main/java/org/apache/ibatis/session/SqlSessionManager.java:329

      throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
    }
    sqlSession.rollback(force);
  }

  @Override
  public List<BatchResult> flushStatements() {
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {
      throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
    }
    return sqlSession.flushStatements();
  }

  @Override
  public void close() {
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {
      throw new SqlSessionException("Error:  Cannot close.  No managed session is started.");
    }
    try {
      sqlSession.close();
    } finally {
      localSqlSession.remove();
    }
  }

  private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
      // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Guard close with isManagedSessionStarted() and close exactly once per started session.
  2. Centralize session lifecycle in one owner (filter/interceptor) and remove ad-hoc close calls elsewhere.
  3. Call close() only after a successful startManagedSession() on the same thread.

Example fix

// before
} finally { sqlSessionManager.close(); }  // throws on second pass / when never started
// after
} finally {
  if (sqlSessionManager.isManagedSessionStarted()) {
    sqlSessionManager.close();
  }
}
Defensive patterns

Strategy: validation

Validate before calling

if (sqlSessionManager.isManagedSessionStarted()) {
  sqlSessionManager.close();
}

Prevention

When it happens

Trigger: Calling close() twice (e.g. application shutdown hook plus request cleanup); close() without any prior startManagedSession(); close() in a finally block after an earlier close already ran.

Common situations: Generic framework cleanup layers that also close the session; refactoring code that moved a close() but left the old one; servlet-filter close racing with explicit close in a controller.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/ed522145056f0590. Report an issue: GitHub.