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
- Remove the transaction phase configuration and keep the observer asynchronous (IN_PROGRESS is the only accepted combination).
- Or drop the async configuration if the observer must run in a transaction phase.
- 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
- Treat async and transaction-phase observer configuration as mutually exclusive.
- Search extension code for configureAsync/observeAsync and transactionPhase co-usage.
- Centralize synthetic-observer registration in one helper that validates the combination once.
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
- Quarkus does not support CDI Full @Specializes annotation; t
- IllegalStateException wrapping ClassNotFoundException for ge
- Unable to derive the logger name at ${injectionPoint}
- Improper integration of '${LogFilterFactory.class.getName()}
- Unable to construct type for ${btRemovedBean}: ${e.getMessag
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/8b3d016717991d44.
Report an issue: GitHub.