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

  1. Annotate the method with @Transactional (its interceptor runs the work on a worker thread) so blocking JDBC is legal there.
  2. For reactive endpoints, add io.smallrye.common.annotation.Blocking to the resource method to shift execution to a worker thread.
  3. 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).
  4. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/c1cd16cb468eaf5a. Report an issue: GitHub.