quarkusio/quarkus · error · IllegalStateException

Synthetic observer declared as asynchronous and transactiona

Error message

Synthetic observer declared as asynchronous and transactional (event type " + syntheticObserver.type + ", \"declared\" by " + syntheticObserver.declaringClass + ", notified using " + syntheticObserver.implementationClass + ")

What it means

Arc rejects synthetic observers that are declared both asynchronous and transactional: an observer registered via SyntheticObserverBuilderImpl with isAsync set and a transactionPhase other than TransactionPhase.IN_PROGRESS is contradictory, because transactional observer semantics only apply to synchronous notification. ExtensionsEntryPoint.registerSyntheticObservers() throws IllegalStateException during deployment to surface the misconfiguration early.

Source

Thrown at independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ExtensionsEntryPoint.java:482

            }
            bean.done();
        }
    }

    /**
     * Must be called <i>after</i> {@code runSynthesis} and <i>before</i> {@code runRegistrationAgain}.
     * <p>
     * It is a no-op if no {@link BuildCompatibleExtension} was found.
     */
    public void registerSyntheticObservers(ObserverRegistrar.RegistrationContext context,
            Predicate<DotName> isApplicationClass) {
        if (invoker.isEmpty()) {
            return;
        }

        for (SyntheticObserverBuilderImpl<?> syntheticObserver : syntheticObservers) {
            if (syntheticObserver.isAsync && syntheticObserver.transactionPhase != TransactionPhase.IN_PROGRESS) {
                throw new IllegalStateException("Synthetic observer declared as asynchronous and transactional "
                        + "(event type " + syntheticObserver.type + ", \"declared\" by " + syntheticObserver.declaringClass
                        + ", notified using " + syntheticObserver.implementationClass + ")");
            }

            ObserverConfigurator observer = context.configure()
                    .beanClass(syntheticObserver.declaringClass)
                    .observedType(syntheticObserver.type)
                    .qualifiers(syntheticObserver.qualifiers.toArray(new org.jboss.jandex.AnnotationInstance[0]))
                    .priority(syntheticObserver.priority)
                    .async(syntheticObserver.isAsync)
                    .transactionPhase(syntheticObserver.transactionPhase);
            configureParams(observer, syntheticObserver.params);
            observer.notify(ng -> {
                BlockCreator bc = ng.notifyMethod();

                // | SyntheticObserver instance = new ConfiguredEventConsumer();
                Expr instance = bc.new_(syntheticObserver.implementationClass);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the transaction phase configuration and keep the observer asynchronous (IN_PROGRESS is the only accepted combination).
  2. Or drop the async configuration if the observer must run in a transaction phase.
  3. Audit all SyntheticObserverBuilder usages in the extension for the async + transactionPhase combination.

Example fix

// before
builder.configureAsync().observeAsync().transactionPhase(TransactionPhase.BEFORE_COMPLETION);

// after (choose one)
builder.configureAsync().observeAsync(); // async, non-transactional
// or
builder.transactionPhase(TransactionPhase.BEFORE_COMPLETION); // synchronous transactional
Defensive patterns

Strategy: validation

Validate before calling

if (builder.isAsync() && builder.getTransactionPhase() != TransactionPhase.IN_PROGRESS) {
    throw new IllegalStateException("Synthetic observer cannot be async and transactional");
}

Type guard

static boolean isLegalObserverConfig(boolean async, TransactionPhase phase) {
    return !async || phase == TransactionPhase.IN_PROGRESS;
}

Try / catch

try {
    extension.registerSyntheticObservers(ctx);
} catch (IllegalStateException e) {
    fail("Invalid synthetic observer configuration: " + e.getMessage());
}

Prevention

When it happens

Trigger: Calling SyntheticObserverBuilder.configureAsync().observeAsync() (or the equivalent flag) together with a transaction phase such as BEFORE_COMPLETION/AFTER_COMPLETION/AFTER_FAILURE/AFTER_SUCCESS on the same synthetic observer, then registering it from a build-time extension.

Common situations: Upgrading an extension that previously used only async observers and adding transaction phases; copying configurator code from a synchronous transactional observer; misunderstanding that @ObservesAsync and @Observes(during=...) are mutually exclusive styles.

Related errors


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