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
- Guard close with isManagedSessionStarted() and close exactly once per started session.
- Centralize session lifecycle in one owner (filter/interceptor) and remove ad-hoc close calls elsewhere.
- 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
- close() is not idempotent on SqlSessionManager — call it exactly once per started session.
- Give the session lifecycle a single owner (filter/interceptor) and delete ad-hoc close calls.
- Guard every close with isManagedSessionStarted().
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
- Error: Cannot get connection. No managed session is starte
- Error: Cannot clear the cache. No managed session is start
- Error: Cannot commit. No managed session is started.
- Error: Cannot rollback. No managed session is started.
- Unknown execution method for: {name}
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/ed522145056f0590.
Report an issue: GitHub.