quarkusio/quarkus · error · BlockingOperationNotAllowedException
You have attempted to perform a blocking operation on a IO t
Error message
You have attempted to perform a blocking operation on a IO thread. This is not allowed, as blocking the IO thread will cause major performance issues with your application. If you want to perform blocking EntityManager operations make sure you are doing it from a worker thread.
What it means
checkBlocking() in TransactionScopedSession guards every EntityManager operation with BlockingOperationControl. Hibernate's EntityManager does JDBC (blocking) I/O, so calling it from a Vert.x event-loop / IO thread would stall the event loop; Quarkus therefore throws BlockingOperationNotAllowedException telling you to run the operation on a worker thread.
Source
Thrown at extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/session/TransactionScopedSession.java:141
false, false);
} else {
throw new ContextNotActiveException(
"Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active."
+ " Consider adding @Transactional to your method to automatically activate a transaction,"
+ " or @ActivateRequestContext if you have valid reasons not to use transactions.");
}
} else {
throw new ContextNotActiveException(
"Cannot use the EntityManager/Session because no transaction is active."
+ " Consider adding @Transactional to your method to automatically activate a transaction,"
+ " or set '" + HibernateOrmRuntimeConfig.extensionPropertyKey("request-scoped.enabled")
+ "' to 'true' if you have valid reasons not to use transactions.");
}
}
private void checkBlocking() {
if (!BlockingOperationControl.isBlockingAllowed()) {
throw new BlockingOperationNotAllowedException(
"You have attempted to perform a blocking operation on a IO thread. This is not allowed, as blocking the IO thread will cause major performance issues with your application. If you want to perform blocking EntityManager operations make sure you are doing it from a worker thread.");
}
}
private boolean isInTransaction() {
try {
switch (transactionManager.getStatus()) {
case Status.STATUS_ACTIVE:
case Status.STATUS_COMMITTING:
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_PREPARED:
case Status.STATUS_PREPARING:
return true;
default:
return false;
}
} catch (Exception e) {
throw new RuntimeException(e);View on GitHub (pinned to e1c734241f)
Solutions
- Annotate the method with @Transactional (its interceptor runs the work on a worker thread) so blocking JDBC is legal there.
- For reactive endpoints, add io.smallrye.common.annotation.Blocking to the resource method to shift execution to a worker thread.
- Move the EntityManager call into a separate @ApplicationScoped bean method annotated @Transactional/@Blocking and call it from the reactive pipeline (e.g. Uni.emitOn or the CDI call itself dispatches).
- Ensure lazy suppliers like Uni/CompletionStage bodies touching the EM actually execute on a worker thread, not during subscription on the event loop.
Example fix
// before
@Path("/users")
public class UserResource {
@Inject EntityManager em;
@GET
public Uni<User> get(long id) {
return Uni.createFrom().item(em.find(User.class, id)); // BlockingOperationNotAllowedException
}
}
// after
@Path("/users")
public class UserResource {
@Inject UserRepository repo;
@GET
public Uni<User> get(long id) {
return repo.find(id); // runs on worker thread
}
}
@ApplicationScoped
class UserRepository {
@Inject EntityManager em;
@Transactional
public User find(long id) { return em.find(User.class, id); }
} Defensive patterns
Strategy: validation
Validate before calling
import io.quarkus.vertx.core.runtime.context.BlockingOperationControl;
boolean emSafeToCallHere() {
return BlockingOperationControl.isBlockingAllowed();
}
// Call before any EntityManager operation when unsure of the thread:
// if (!emSafeToCallHere()) dispatchToWorkerThread(...); Try / catch
try {
em.find(User.class, id);
} catch (BlockingOperationNotAllowedException e) {
// we were on the event loop: re-dispatch to a worker-thread bean
return Uni.createFrom().item(() -> transactionalRepo.find(id))
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
.await().indefinitely();
} Prevention
- Annotate blocking ORM entry points with @Transactional or @Blocking so Quarkus moves them to a worker thread.
- Never touch the EntityManager inside Uni/CompletionStage suppliers that may run on the IO thread.
- Keep reactive endpoints free of JDBC/ORM code; delegate to @ApplicationScoped transactional services.
- Test reactive endpoints with RestAssured/real HTTP so they execute on the same thread model as production.
When it happens
Trigger: Calling persist, merge, remove, find, findMultiple, getReference (or any other method on the injected EntityManager) while BlockingOperationControl.isBlockingAllowed() is false — i.e. from a Vert.x event-loop thread: inside a reactive REST endpoint that never leaves the IO thread, a Vert.x handler/worker-free route, a reactive gRPC/GraphQL resolver executing on the loop, or a messaging callback without @Blocking.
Common situations: Mixing reactive endpoints with blocking Hibernate; forgetting @Blocking or missing an @Transactional boundary that would dispatch to a worker thread; calling the EM from a Vert.x EventBus consumer or timer set directly on the event loop; Uni.createFrom().item(() -> em.find(...)) evaluated eagerly on the IO thread.
Related errors
- Attempting a blocking read on io thread
- Blocking gRPC client call made from the event loop. If the c
- You have attempted to perform a blocking operation on a IO t
- You have attempted to inject AuthzClient on a IO thread. Thi
- @Transactional cannot start a JTA transaction within a react
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/c1cd16cb468eaf5a.
Report an issue: GitHub.