diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index e697541501d524..24d0103a142e22 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -3780,6 +3780,11 @@
quarkus-resteasy-common-spi
${project.version}
+
+ io.quarkus
+ quarksu-resteasy-common-deployment-spi
+ ${project.version}
+
io.reactivex.rxjava2
rxjava
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 7f86c1174b30f5..1b866dc2c2165c 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -59,6 +59,7 @@
resteasy-classic
+ resteasy-common
smallrye-openapi-common
smallrye-openapi
swagger-ui
diff --git a/extensions/resteasy-common/deployment-spi/pom.xml b/extensions/resteasy-common/deployment-spi/pom.xml
new file mode 100644
index 00000000000000..d4378386eb930e
--- /dev/null
+++ b/extensions/resteasy-common/deployment-spi/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-resteasy-common-spi-parent
+ 999-SNAPSHOT
+
+
+ quarksu-resteasy-common-deployment-spi
+ Quarkus - Resteasy - SPI - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.smallrye
+ jandex
+
+
+
+
diff --git a/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java b/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java
new file mode 100644
index 00000000000000..f3b9b7bc34fd09
--- /dev/null
+++ b/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java
@@ -0,0 +1,20 @@
+package io.quarkus.resteasy.common.deployment;
+
+import java.util.function.Predicate;
+
+import org.jboss.jandex.ClassInfo;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class EndpointValidationPredicatesBuildItem extends MultiBuildItem {
+
+ private final Predicate predicate;
+
+ public EndpointValidationPredicatesBuildItem(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ public Predicate getPredicate() {
+ return predicate;
+ }
+}
diff --git a/extensions/resteasy-common/pom.xml b/extensions/resteasy-common/pom.xml
new file mode 100644
index 00000000000000..86b66d80bd60a0
--- /dev/null
+++ b/extensions/resteasy-common/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-extensions-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-resteasy-common-spi-parent
+ Quarkus - JMS SPI - Parent
+ pom
+
+ deployment-spi
+
+
+
diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml
index bc8a8c616760aa..4c40a3460fdf30 100644
--- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml
+++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml
@@ -33,6 +33,10 @@
io.quarkus
quarkus-rest-spi-deployment
+
+ io.quarkus
+ quarksu-resteasy-common-deployment-spi
+
io.quarkus
quarkus-rest-client-jaxrs
diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
index 2f4ac2e80302f5..2f8b3722a6a67f 100644
--- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
+++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
@@ -50,6 +50,7 @@
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.RuntimeType;
@@ -170,6 +171,7 @@
import io.quarkus.jaxrs.client.reactive.runtime.RestClientBase;
import io.quarkus.jaxrs.client.reactive.runtime.ToObjectArray;
import io.quarkus.jaxrs.client.reactive.runtime.impl.MultipartResponseDataBase;
+import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.ParameterContainersBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.QuarkusFactoryCreator;
@@ -289,7 +291,8 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder,
List defaultProduces,
List disableSmartDefaultProduces,
List disableRemovalTrailingSlashProduces,
- List parameterContainersBuildItems) {
+ List parameterContainersBuildItems,
+ List validationPredicatesBuildItems) {
String defaultConsumesType = defaultMediaType(defaultConsumes, MediaType.APPLICATION_OCTET_STREAM);
String defaultProducesType = defaultMediaType(defaultProduces, MediaType.TEXT_PLAIN);
@@ -343,6 +346,8 @@ public boolean test(Map anns) {
return anns.containsKey(NOT_BODY) || anns.containsKey(URL);
}
})
+ .setValidateEndpoint(validationPredicatesBuildItems.stream().map(item -> item.getPredicate())
+ .collect(Collectors.toUnmodifiableList()))
.setResourceMethodCallback(new Consumer<>() {
@Override
public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) {
diff --git a/extensions/resteasy-reactive/rest/deployment/pom.xml b/extensions/resteasy-reactive/rest/deployment/pom.xml
index a506c92c91c7a8..12b9cda33db485 100644
--- a/extensions/resteasy-reactive/rest/deployment/pom.xml
+++ b/extensions/resteasy-reactive/rest/deployment/pom.xml
@@ -46,6 +46,10 @@
io.quarkus
quarkus-security-spi
+
+ io.quarkus
+ quarksu-resteasy-common-deployment-spi
+
io.quarkus
quarkus-jsonp-deployment
diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
index 35ce4ab24bddea..0e47168c16a723 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
@@ -14,6 +14,7 @@
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -159,6 +160,7 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem;
+import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.AggregatedParameterContainersBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.FactoryUtils;
@@ -468,7 +470,8 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem,
CompiledJavaVersionBuildItem compiledJavaVersionBuildItem,
ResourceInterceptorsBuildItem resourceInterceptorsBuildItem,
Capabilities capabilities,
- Optional allowNotRestParametersBuildItem) {
+ Optional allowNotRestParametersBuildItem,
+ List validationPredicatesBuildItems) {
if (!resourceScanningResultBuildItem.isPresent()) {
// no detected @Path, bail out
@@ -640,6 +643,8 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an
})
.setResteasyReactiveRecorder(recorder)
.setApplicationClassPredicate(applicationClassPredicate)
+ .setValidateEndpoint(validationPredicatesBuildItems.stream().map(item -> item.getPredicate())
+ .collect(Collectors.toUnmodifiableList()))
.setTargetJavaVersion(new TargetJavaVersion() {
private final Status result;
@@ -849,6 +854,18 @@ private FilterClassIntrospector createFilterClassIntrospector() {
return ab.get();
}
+ @BuildStep
+ EndpointValidationPredicatesBuildItem createSpringRestControllerPredicate() {
+ Predicate predicate = new Predicate() {
+ @Override
+ public boolean test(ClassInfo classInfo) {
+ return Modifier.isInterface(classInfo.flags())
+ || Modifier.isAbstract(classInfo.flags());
+ }
+ };
+ return new EndpointValidationPredicatesBuildItem(predicate);
+ }
+
// We want to add @Typed to resources, beanparams and providers so that they can be resolved as CDI bean using purely their
// class as a bean type. This removes any ambiguity that potential subclasses may have.
@BuildStep
diff --git a/extensions/spring-web/core/deployment/pom.xml b/extensions/spring-web/core/deployment/pom.xml
index 3e4a4c85b14145..59044073bf2c68 100644
--- a/extensions/spring-web/core/deployment/pom.xml
+++ b/extensions/spring-web/core/deployment/pom.xml
@@ -44,6 +44,10 @@
io.quarkus
quarkus-resteasy-common-spi
+
+ io.quarkus
+ quarksu-resteasy-common-deployment-spi
+
diff --git a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
index 4699cc95931415..796bcef677ef0e 100644
--- a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
+++ b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
@@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.HttpHeaders;
@@ -34,6 +35,7 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
+import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem;
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem;
@@ -86,6 +88,18 @@ public AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResource
return new AdditionalJaxRsResourceMethodAnnotationsBuildItem(MAPPING_ANNOTATIONS);
}
+ @BuildStep
+ EndpointValidationPredicatesBuildItem createSpringRestControllerPredicate() {
+ Predicate predicate = new Predicate<>() {
+ @Override
+ public boolean test(ClassInfo classInfo) {
+ return classInfo
+ .declaredAnnotation(REST_CONTROLLER_ANNOTATION) == null;
+ }
+ };
+ return new EndpointValidationPredicatesBuildItem(predicate);
+ }
+
@BuildStep
public void ignoreReflectionHierarchy(BuildProducer ignore) {
ignore.produce(new ReflectiveHierarchyIgnoreWarningBuildItem(
diff --git a/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java b/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java
new file mode 100644
index 00000000000000..9cdb6b44c87798
--- /dev/null
+++ b/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java
@@ -0,0 +1,48 @@
+package io.quarkus.spring.web.resteasy.reactive.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+import jakarta.ws.rs.QueryParam;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class UnbuildableSpringRestControllerInterfaceTest {
+
+ @RegisterExtension
+ static final QuarkusProdModeTest config = new QuarkusProdModeTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(UnbuildableControllerInterface.class))
+ .setApplicationName("unbuildable-rest-controller-interface")
+ .setApplicationVersion("0.1-SNAPSHOT")
+ .assertBuildException(throwable -> {
+ assertThat(throwable).isInstanceOf(RuntimeException.class);
+ assertThat(throwable).hasMessageContaining(
+ "Cannot have more than one of @PathParam, @QueryParam, @HeaderParam, @FormParam, @CookieParam, @BeanParam, @Context on method");
+ });
+
+ @Test
+ public void testBuildLogs() {
+ fail("Should not be called");
+ }
+
+ @RestController
+ @RequestMapping("/unbuildable")
+ public interface UnbuildableControllerInterface {
+ @GetMapping("/ping")
+ String ping();
+
+ @PostMapping("/hello")
+ String hello(@RequestParam(required = false) @QueryParam("dung") String params);
+
+ }
+
+}
diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
index 25633cdd60993d..63aa10ffb1b6b1 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
@@ -239,6 +239,7 @@ public abstract class EndpointIndexer> isDisabledCreator;
private final Predicate