diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index aecfcfa96f848..03fbb4cb2d221 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -49,7 +49,7 @@
0.21.0
- 1.7.0.Alpha13
+ 1.7.0.Final
jdt_apt
diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml
index fa9dc00ae202f..720a01b64e47c 100644
--- a/independent-projects/arc/pom.xml
+++ b/independent-projects/arc/pom.xml
@@ -58,7 +58,7 @@
1.6.4
5.3.1
- 1.7.0.Alpha14
+ 1.7.0.Final
2.0.1
4.0.9
4.13.2
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java
index f5fb1364f1109..8af6b4fefca8d 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java
@@ -104,6 +104,8 @@ public class BeanDeployment {
private final Set removedBeans;
+ private final Set beansWithRuntimeDeferredUnproxyableError;
+
private final Map> customContexts;
private final Map beanDefiningAnnotations;
@@ -149,6 +151,7 @@ public class BeanDeployment {
this.removeUnusedBeans = builder.removeUnusedBeans;
this.unusedExclusions = removeUnusedBeans ? new ArrayList<>(builder.removalExclusions) : null;
this.removedBeans = removeUnusedBeans ? new CopyOnWriteArraySet<>() : Collections.emptySet();
+ this.beansWithRuntimeDeferredUnproxyableError = Collections.newSetFromMap(new ConcurrentHashMap<>());
this.customContexts = new ConcurrentHashMap<>();
this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList();
@@ -506,6 +509,14 @@ public Collection getRemovedBeans() {
return Collections.unmodifiableSet(removedBeans);
}
+ boolean hasRuntimeDeferredUnproxyableError(BeanInfo bean) {
+ return beansWithRuntimeDeferredUnproxyableError.contains(bean);
+ }
+
+ void deferUnproxyableErrorToRuntime(BeanInfo bean) {
+ beansWithRuntimeDeferredUnproxyableError.add(bean);
+ }
+
public Collection getQualifiers() {
return Collections.unmodifiableCollection(qualifiers.values());
}
@@ -1522,6 +1533,17 @@ private void validateBeans(List errors, Consumer
Map> namedBeans = new HashMap<>();
Set classesReceivingNoArgsCtor = new HashSet<>();
+ // this set is only used in strict compatible mode (see `Beans.validateBean()`),
+ // so no need to initialize it otherwise
+ Set injectedBeans = new HashSet<>();
+ if (strictCompatibility) {
+ for (InjectionPointInfo injectionPoint : this.injectionPoints) {
+ if (injectionPoint.hasResolvedBean()) {
+ injectedBeans.add(injectionPoint.getResolvedBean());
+ }
+ }
+ }
+
for (BeanInfo bean : beans) {
if (bean.getName() != null) {
List named = namedBeans.get(bean.getName());
@@ -1532,7 +1554,7 @@ private void validateBeans(List errors, Consumer
named.add(bean);
findNamespaces(bean, namespaces);
}
- bean.validate(errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor);
+ bean.validate(errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor, injectedBeans);
}
if (!namedBeans.isEmpty()) {
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java
index 15af009794e8a..40f5469aa85b4 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java
@@ -30,6 +30,7 @@
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.CreationException;
import jakarta.enterprise.inject.IllegalProductException;
+import jakarta.enterprise.inject.UnproxyableResolutionException;
import jakarta.enterprise.inject.literal.InjectLiteral;
import jakarta.enterprise.inject.spi.InterceptionType;
import jakarta.interceptor.InvocationContext;
@@ -1927,7 +1928,10 @@ protected void implementGet(BeanInfo bean, ClassCreator beanCreator, ProviderTyp
MethodCreator get = beanCreator.getMethodCreator("get", providerType.descriptorName(), CreationalContext.class)
.setModifiers(ACC_PUBLIC);
- if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
+ if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) {
+ get.throwException(UnproxyableResolutionException.class, "Bean not proxyable: " + bean);
+ get.returnValue(get.loadNull());
+ } else if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
// @Dependent pseudo-scope
// Foo instance = create(ctx)
ResultHandle instance = get.invokeVirtualMethod(
@@ -2214,6 +2218,10 @@ private ResultHandle wrapCurrentInjectionPoint(BeanInfo bean,
}
private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCreator) {
+ if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) {
+ return;
+ }
+
// Add proxy volatile field
String proxyTypeName = getProxyTypeName(bean, baseName);
beanCreator.getFieldCreator(FIELD_NAME_PROXY, proxyTypeName)
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java
index 160e9cb46cba8..94b5567a1df75 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java
@@ -582,8 +582,8 @@ public String getClientProxyPackageName() {
}
void validate(List errors, Consumer bytecodeTransformerConsumer,
- Set classesReceivingNoArgsCtor) {
- Beans.validateBean(this, errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor);
+ Set classesReceivingNoArgsCtor, Set injectedBeans) {
+ Beans.validateBean(this, errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor, injectedBeans);
}
void validateInterceptorDecorator(List errors, Consumer bytecodeTransformerConsumer) {
@@ -797,7 +797,7 @@ private void putLifecycleInterceptors(Map li
private void addClassLevelBindings(ClassInfo targetClass, Collection bindings) {
List classLevelBindings = new ArrayList<>();
- doAddClassLevelBindings(targetClass, classLevelBindings, Set.of());
+ doAddClassLevelBindings(targetClass, classLevelBindings, Set.of(), false);
bindings.addAll(classLevelBindings);
if (!stereotypes.isEmpty()) {
// interceptor binding declared on a bean class replaces an interceptor binding of the same type
@@ -808,22 +808,27 @@ private void addClassLevelBindings(ClassInfo targetClass, Collection bindings, Set skip) {
+ private void doAddClassLevelBindings(ClassInfo classInfo, Collection bindings, Set skip,
+ boolean onlyInherited) {
beanDeployment.getAnnotations(classInfo).stream()
.filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null)
.filter(a -> !skip.contains(a.name()))
+ .filter(a -> !onlyInherited
+ || beanDeployment.hasAnnotation(beanDeployment.getInterceptorBinding(a.name()), DotNames.INHERITED))
.forEach(bindings::add);
if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotNames.OBJECT)) {
ClassInfo superClass = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName());
if (superClass != null) {
- doAddClassLevelBindings(superClass, bindings, skip);
+ // proper interceptor binding inheritance only in strict mode, due to Quarkus expecting security
+ // annotations (such as `@RolesAllowed`) to be inherited, even though they are not `@Inherited`
+ doAddClassLevelBindings(superClass, bindings, skip, beanDeployment.strictCompatibility);
}
}
}
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java
index f6200b874084f..31ba850b8f231 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java
@@ -711,25 +711,34 @@ static void validateInterceptorDecorator(BeanInfo bean, List errors,
}
static void validateBean(BeanInfo bean, List errors, Consumer bytecodeTransformerConsumer,
- Set classesReceivingNoArgsCtor) {
+ Set classesReceivingNoArgsCtor, Set injectedBeans) {
+
+ // by default, we fail deployment due to unproxyability for all beans, but in strict mode,
+ // we only do that for beans that are injected somewhere -- and defer the error to runtime otherwise,
+ // due to CDI spec requirements
+ boolean failIfNotProxyable = bean.getDeployment().strictCompatibility ? injectedBeans.contains(bean) : true;
+
if (bean.isClassBean()) {
ClassInfo beanClass = bean.getTarget().get().asClass();
String classifier = bean.getScope().isNormal() ? "Normal scoped" : null;
if (classifier == null && bean.isSubclassRequired()) {
classifier = "Intercepted";
+ failIfNotProxyable = true;
}
if (Modifier.isFinal(beanClass.flags()) && classifier != null) {
// Client proxies and subclasses require a non-final class
if (bean.getDeployment().transformUnproxyableClasses) {
bytecodeTransformerConsumer
.accept(new BytecodeTransformer(beanClass.name().toString(), new FinalClassTransformFunction()));
- } else {
+ } else if (failIfNotProxyable) {
errors.add(new DeploymentException(String.format("%s bean must not be final: %s", classifier, bean)));
+ } else {
+ bean.getDeployment().deferUnproxyableErrorToRuntime(bean);
}
}
if (bean.getDeployment().strictCompatibility && classifier != null) {
- validateNonStaticFinalMethods(beanClass, bean.getDeployment().getBeanArchiveIndex(),
- classifier, errors);
+ validateNonStaticFinalMethods(bean, beanClass, bean.getDeployment().getBeanArchiveIndex(),
+ classifier, errors, failIfNotProxyable);
}
MethodInfo noArgsConstructor = beanClass.method(Methods.INIT);
@@ -754,13 +763,16 @@ static void validateBean(BeanInfo bean, List errors, Consumer errors, Consumer errors, Consumer errors, Consumer errors, Consumer errors) {
+ private static void validateNonStaticFinalMethods(BeanInfo bean, ClassInfo clazz, IndexView beanArchiveIndex,
+ String classifier, List errors, boolean failIfNotProxyable) {
// see also Methods.skipForClientProxy()
while (!clazz.name().equals(DotNames.OBJECT)) {
for (MethodInfo method : clazz.methods()) {
@@ -920,9 +936,13 @@ private static void validateNonStaticFinalMethods(ClassInfo clazz, IndexView bea
}
if (Modifier.isFinal(method.flags())) {
- errors.add(new DeploymentException(String.format(
- "%s bean must not declare non-static final methods with public, protected or default visibility: %s",
- classifier, method)));
+ if (failIfNotProxyable) {
+ errors.add(new DeploymentException(String.format(
+ "%s bean must not declare non-static final methods with public, protected or default visibility: %s",
+ classifier, method)));
+ } else {
+ bean.getDeployment().deferUnproxyableErrorToRuntime(bean);
+ }
}
}
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
index ea0277af08f00..8421c9aed59ee 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
@@ -78,6 +78,12 @@ public ClientProxyGenerator(Predicate applicationClassPredicate, boolea
Collection generate(BeanInfo bean, String beanClassName,
Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses) {
+ // see `BeanGenerator` -- if this bean is unproxyable and that error is deferred to runtime,
+ // we don't need to (and cannot, in fact) generate the client proxy class
+ if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) {
+ return Collections.emptySet();
+ }
+
ProviderType providerType = new ProviderType(bean.getProviderType());
ClassInfo providerClass = getClassByName(bean.getDeployment().getBeanArchiveIndex(), providerType.name());
String baseName = getBaseName(bean, beanClassName);
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java
index c6c3c7e4df94a..5f57163fe681e 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java
@@ -183,18 +183,20 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea
skipPredicate.startProcessing(classInfo, originalClassInfo);
for (MethodInfo method : classInfo.methods()) {
+ MethodKey key = new MethodKey(method);
+ if (candidates.containsKey(key)) {
+ continue;
+ }
+
// Note that we must merge the bindings first
Set bindings = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings,
ignoreMethodLevelBindings, method, noClassInterceptorsMethods);
- if (bindings.isEmpty() && !targetHasAroundInvokes) {
- // No bindings found and target class does not declare around invoke interceptor methods
- continue;
- }
+ boolean possiblyIntercepted = !bindings.isEmpty() || targetHasAroundInvokes;
if (skipPredicate.test(method)) {
continue;
}
boolean addToCandidates = true;
- if (Modifier.isFinal(method.flags())) {
+ if (Modifier.isFinal(method.flags()) && possiblyIntercepted) {
if (transformUnproxyableClasses && !isNoninterceptableKotlinMethod(method)) {
methodsFromWhichToRemoveFinal.add(NameAndDescriptor.fromMethodInfo(method));
} else {
@@ -203,7 +205,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea
}
}
if (addToCandidates) {
- candidates.computeIfAbsent(new Methods.MethodKey(method), key -> bindings);
+ candidates.putIfAbsent(key, bindings);
}
}
skipPredicate.methodsProcessed();
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java
index 63bce6bf5fd68..dbca61638c8d1 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java
@@ -64,6 +64,12 @@ default InjectableBean getBean() {
*/
@Override
default void close() {
+ // https://github.com/quarkusio/quarkus/issues/33665
+ if (Arc.container().strictCompatibility()) {
+ destroy();
+ return;
+ }
+
InjectableBean bean = getBean();
if (bean == null || Dependent.class.equals(bean.getScope())) {
destroy();
diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml
index 844b4b621e59a..87e459298438e 100644
--- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml
+++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml
@@ -60,11 +60,7 @@
-
-
-
-
@@ -85,27 +81,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java
index 6855a1c35a9b0..05b799d33fde4 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java
@@ -29,6 +29,7 @@
import org.junit.jupiter.api.extension.ExtensionContext;
import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcInitConfig;
import io.quarkus.arc.ComponentsProvider;
import io.quarkus.arc.ResourceReferenceProvider;
import io.quarkus.arc.processor.AlternativePriorities;
@@ -468,7 +469,7 @@ public void writeResource(Resource resource) throws IOException {
.setContextClassLoader(testClassLoader);
// Now we are ready to initialize Arc
- Arc.initialize();
+ Arc.initialize(ArcInitConfig.builder().setStrictCompatibility(strictCompatibility).build());
} catch (Throwable e) {
if (shouldFail) {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java
similarity index 76%
rename from independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java
rename to independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java
index fc7da12739ff8..748735b30dbfe 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java
@@ -5,17 +5,19 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.spi.DeploymentException;
+import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.arc.test.ArcTestContainer;
-public class FinalMethodIllegalTest {
+public class FinalMethodIllegalWhenInjectedTest {
@RegisterExtension
public ArcTestContainer container = ArcTestContainer.builder()
- .beanClasses(Moo.class)
+ .beanClasses(Moo.class, MooConsumer.class)
.strictCompatibility(true)
.shouldFail()
.build();
@@ -34,4 +36,11 @@ final int getVal() {
return -1;
}
}
+
+ // to trigger deployment-time error (in strict compatible mode)
+ @Dependent
+ static class MooConsumer {
+ @Inject
+ Moo moo;
+ }
}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java
new file mode 100644
index 0000000000000..d82d2a136e437
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java
@@ -0,0 +1,35 @@
+package io.quarkus.arc.test.clientproxy.finalmethod;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.UnproxyableResolutionException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
+
+public class FinalMethodIllegalWhenNotInjectedTest {
+ @RegisterExtension
+ public ArcTestContainer container = ArcTestContainer.builder()
+ .beanClasses(Moo.class)
+ .strictCompatibility(true)
+ .shouldFail()
+ .build();
+
+ @Test
+ public void test() {
+ assertThrows(UnproxyableResolutionException.class, () -> {
+ Arc.container().instance(Moo.class).get();
+ });
+ }
+
+ @ApplicationScoped
+ static class Moo {
+ final int getVal() {
+ return -1;
+ }
+ }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java
new file mode 100644
index 0000000000000..4cd5c46fc9198
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java
@@ -0,0 +1,81 @@
+package io.quarkus.arc.test.instance.destroy;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.Dependent;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.test.ArcTestContainer;
+
+public class InstanceHandleDestroyTest {
+ @RegisterExtension
+ public ArcTestContainer container = new ArcTestContainer.Builder()
+ .beanClasses(MyDependentBean.class, MyAppScopedBean.class)
+ .strictCompatibility(true)
+ .build();
+
+ @Test
+ public void testDestroy() {
+ assertFalse(MyDependentBean.DESTROYED.get());
+ try (InstanceHandle handle = Arc.container().instance(MyDependentBean.class)) {
+ assertNotNull(handle.get().toString());
+ }
+ assertTrue(MyDependentBean.DESTROYED.get());
+
+ // normal-scoped
+ String oldId;
+ assertFalse(MyAppScopedBean.DESTROYED.get());
+ try (InstanceHandle handle = Arc.container().instance(MyAppScopedBean.class)) {
+ assertNotNull(handle.get().toString());
+ oldId = handle.get().getId();
+ }
+ assertTrue(MyAppScopedBean.DESTROYED.get());
+
+ String newId = Arc.container().instance(MyAppScopedBean.class).get().getId();
+ assertNotEquals(oldId, newId);
+ }
+
+ @Dependent
+ static class MyDependentBean {
+ static final AtomicBoolean DESTROYED = new AtomicBoolean(false);
+
+ @PreDestroy
+ void destroy() {
+ DESTROYED.set(true);
+ }
+ }
+
+ @ApplicationScoped
+ static class MyAppScopedBean {
+ static final AtomicBoolean DESTROYED = new AtomicBoolean(false);
+
+ String id;
+
+ String getId() {
+ return id;
+ }
+
+ @PostConstruct
+ void init() {
+ this.id = UUID.randomUUID().toString();
+ }
+
+ @PreDestroy
+ void destroy() {
+ DESTROYED.set(true);
+ }
+ }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java
new file mode 100644
index 0000000000000..6f08c6cf41389
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java
@@ -0,0 +1,96 @@
+package io.quarkus.arc.test.interceptors.bindings.inherited;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import jakarta.annotation.Priority;
+import jakarta.inject.Singleton;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InterceptorBinding;
+import jakarta.interceptor.InvocationContext;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
+
+public class InheritedBindingOnBeanTest {
+ @RegisterExtension
+ public ArcTestContainer container = new ArcTestContainer.Builder()
+ .beanClasses(MyBean.class, FooBinding.class, BarBinding.class, FooInterceptor.class, BarInterceptor.class)
+ .strictCompatibility(true) // correct interceptor binding inheritance
+ .build();
+
+ @Test
+ public void testInterception() {
+ MyBean bean = Arc.container().instance(MyBean.class).get();
+ assertNotNull(bean);
+ bean.doSomething();
+ assertTrue(FooInterceptor.intercepted);
+ assertFalse(BarInterceptor.intercepted);
+ }
+
+ @FooBinding
+ @BarBinding
+ static class MySuperclass {
+ }
+
+ @Singleton
+ static class MyBean extends MySuperclass {
+ void doSomething() {
+ }
+ }
+
+ @Target({ TYPE, METHOD })
+ @Retention(RUNTIME)
+ @Documented
+ @InterceptorBinding
+ @Inherited
+ @interface FooBinding {
+ }
+
+ @Target({ TYPE, METHOD })
+ @Retention(RUNTIME)
+ @Documented
+ @InterceptorBinding
+ // not @Inherited
+ @interface BarBinding {
+ }
+
+ @FooBinding
+ @Interceptor
+ @Priority(1)
+ static class FooInterceptor {
+ static boolean intercepted = false;
+
+ @AroundInvoke
+ Object intercept(InvocationContext ctx) throws Exception {
+ intercepted = true;
+ return ctx.proceed();
+ }
+ }
+
+ @BarBinding
+ @Interceptor
+ @Priority(1)
+ static class BarInterceptor {
+ static boolean intercepted = false;
+
+ @AroundInvoke
+ Object intercept(InvocationContext ctx) throws Exception {
+ intercepted = true;
+ return ctx.proceed();
+ }
+ }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java
new file mode 100644
index 0000000000000..590648eaba2de
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java
@@ -0,0 +1,72 @@
+package io.quarkus.arc.test.interceptors.bindings.inherited;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.Dependent;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InterceptorBinding;
+import jakarta.interceptor.InvocationContext;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
+
+public class InheritedMethodsWithInterceptorBindingTest {
+ @RegisterExtension
+ ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class);
+
+ @Test
+ public void test() {
+ MyBean bean = Arc.container().instance(MyBean.class).get();
+ assertEquals("foobar", bean.foobar());
+ assertEquals("intercepted: foobar", bean.foobarNotInherited());
+ }
+
+ static class MySuperclass {
+ @MyInterceptorBinding
+ String foobar() {
+ return "this should be ignored";
+ }
+
+ @MyInterceptorBinding
+ String foobarNotInherited() {
+ return "foobar";
+ }
+ }
+
+ @Dependent
+ static class MyBean extends MySuperclass {
+ @Override
+ String foobar() {
+ return "foobar";
+ }
+ }
+
+ @Target({ ElementType.TYPE, ElementType.METHOD })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ @InterceptorBinding
+ @interface MyInterceptorBinding {
+ }
+
+ @MyInterceptorBinding
+ @Priority(1)
+ @Interceptor
+ static class MyInterceptor {
+ @AroundInvoke
+ public Object intercept(InvocationContext ctx) throws Exception {
+ return "intercepted: " + ctx.proceed();
+ }
+ }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java
index a844883ff8152..299571408e9f1 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java
@@ -6,6 +6,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -15,5 +16,6 @@
@Retention(RUNTIME)
@Documented
@InterceptorBinding
+@Inherited
public @interface InheritedClassLevel {
}