diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index c7526647b8595c..59f1879ee916a4 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -266,6 +266,11 @@
quarkus-resteasy-jaxb-deployment
${project.version}
+
+ io.quarkus
+ quarkus-cxf-deployment
+ ${project.version}
+
io.quarkus
quarkus-smallrye-jwt-deployment
diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index 5b86261c611c3c..0b310dc48e9432 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -173,6 +173,11 @@
1.0.1.Final
8.0.1
1.13.0
+ 1.2
+ 3.3.4
+ 2.0.0.Final
+ 1.4.0
+ 1.1.1
@@ -542,6 +547,11 @@
quarkus-resteasy-server-common
${project.version}
+
+ io.quarkus
+ quarkus-cxf
+ ${project.version}
+
io.quarkus
quarkus-narayana-jta
@@ -1303,6 +1313,11 @@
jboss-jaxb-api_2.3_spec
${jboss-jaxb-api_2.3_spec.version}
+
+ jakarta.jws
+ jakarta.jws-api
+ ${jakarta-jws-api.version}
+
org.jboss.spec.javax.ws.rs
jboss-jaxrs-api_2.1_spec
@@ -1328,6 +1343,50 @@
artemis-commons
${artemis.version}
+
+ javax.xml.soap
+ javax.xml.soap-api
+ ${javax-xml-soap.version}
+ provided
+
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxws
+ ${cxf.version}
+
+
+ org.jboss.spec.javax.annotation
+ jboss-annotations-api_1.3_spec
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ javax.activation
+ activation
+
+
+ javax.xml.soap
+ javax.xml.soap-api
+
+
+
+
+ org.apache.cxf
+ cxf-rt-transports-http
+ ${cxf.version}
+
+
+ org.jboss.spec.javax.annotation
+ jboss-annotations-api_1.3_spec
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+
org.apache.httpcomponents
httpclient
@@ -1648,6 +1707,17 @@
stm
${narayana.version}
+
+ org.jboss.spec.javax.xml.ws
+ jboss-jaxws-api_2.3_spec
+ ${jaxws.version}
+
+
+ org.jboss.spec.javax.annotation
+ jboss-annotations-api_1.3_spec
+
+
+
org.jboss.resteasy
resteasy-core
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
index afc54a00a8a52d..310d57c7fa7536 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java
@@ -14,6 +14,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String ARTEMIS_CORE = "artemis-core";
public static final String ARTEMIS_JMS = "artemis-jms";
public static final String CDI = "cdi";
+ public static final String CXF = "cxf";
public static final String DYNAMODB = "dynamodb";
public static final String ELASTICSEARCH_REST_CLIENT = "elasticsearch-rest-client";
public static final String FLYWAY = "flyway";
diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc
index 56b6c557bff6cf..05536f14ec773e 100644
--- a/docs/src/main/asciidoc/index.adoc
+++ b/docs/src/main/asciidoc/index.adoc
@@ -58,6 +58,7 @@ include::quarkus-intro.adoc[tag=intro]
* link:jms.html[Using Artemis JMS Client]
* link:reactive-routes.adoc[Using Reactive Routes]
* link:camel.adoc[Apache Camel]
+* link:soap-web-service-guide.html[Writing SOAP Web Services]
* link:faq.html[FAQs]
diff --git a/docs/src/main/asciidoc/soap-web-service-guide.adoc b/docs/src/main/asciidoc/soap-web-service-guide.adoc
new file mode 100644
index 00000000000000..94f4987dff7724
--- /dev/null
+++ b/docs/src/main/asciidoc/soap-web-service-guide.adoc
@@ -0,0 +1,244 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
+////
+= Quarkus - Writing Soap Web Services
+
+include::./attributes.adoc[]
+
+SOAP (Simple Object Access Protocol) is an exchange protocole used by old web service before the developement of REST technology.
+
+In this guide, we see how you can get your web services to consume and produce SOAP payloads.
+
+== Prerequisites
+
+To complete this guide, you need:
+
+* less than 15 minutes
+* an IDE
+* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
+* Apache Maven 3.5.3+
+
+== Architecture
+
+The application built in this guide is quite simple: the user can add elements in a list using a form and the list is updated.
+
+All the information between the browser and the server are formatted as SOAP.
+
+== Solution
+
+We recommend that you follow the instructions in the next sections and create the application step by step.
+However, you can go right to the completed example.
+
+Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].
+
+The solution is located in the `cxf` {quickstarts-tree-url}/cxf[directory].
+
+== Creating the Maven project
+
+First, we need a new project. Create a new project with the following command:
+
+[source,shell,subs=attributes+]
+----
+mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
+ -DprojectGroupId=org.acme \
+ -DprojectArtifactId=jaxws \
+ -Dextensions="cxf"
+----
+
+This command generates a Maven structure importing the CXF/JAX-WS extensions.
+
+== Creating your first SOAP Web service
+
+In this example, we will create an application to manage a list of fruits.
+
+First, let's create the `Fruit` bean as follows:
+
+[source,java]
+----
+package org.acme.cxf;
+
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+@XmlJavaTypeAdapter(FruitAdapter.class)
+public interface Fruit {
+
+ String getName();
+
+ String getDescription();
+}
+----
+
+And it's implementation `FruitImpl`:
+
+[source,java]
+----
+package org.acme.cxf.impl;
+
+import java.util.Objects;
+import org.acme.cxf.Fruit;
+
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name = "Fruit")
+public class FruitImpl implements Fruit {
+
+ private String name;
+
+ private String description;
+
+ public FruitImpl() {
+ }
+
+ public FruitImpl(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Fruit)) {
+ return false;
+ }
+
+ Fruit other = (Fruit) obj;
+
+ return Objects.equals(other.getName(), this.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.getName());
+ }
+}
+----
+
+To finish about the data the `FruitAdapter`
+
+[source,java]
+----
+package org.acme.cxf;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+import org.acme.cxf.Fruit;
+import org.acme.cxf.impl.FruitImpl;
+
+public class FruitAdapter extends XmlAdapter {
+ public FruitImpl marshal(Fruit Fruit) throws Exception {
+ if (Fruit instanceof FruitImpl) {
+ return (FruitImpl) Fruit;
+ }
+ return new FruitImpl(Fruit.getName(), Fruit.getDescription());
+ }
+
+ public Fruit unmarshal(FruitImpl Fruit) throws Exception {
+ return Fruit;
+ }
+}
+----
+
+
+
+Now, create the `org.acme.cxf.soap.FruitWebService` class as follows:
+
+[source,java]
+----
+package org.acme.cxf;
+
+import java.util.Set;
+
+import javax.jws.WebMethod;
+import javax.jws.WebParam;
+import javax.jws.WebService;
+
+@WebService
+public interface FruitWebService {
+
+ @WebMethod
+ Set list();
+
+ @WebMethod
+ Set add(Fruit fruit);
+
+ @WebMethod
+ Set delete(Fruit fruit);
+}
+----
+
+Then, create the `org.acme.cxf.soap.impl.FruitWebServiceImpl` class as follows:
+
+[source,java]
+----
+package org.acme.cxf.impl;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import javax.jws.WebService;
+
+import org.acme.cxf.Fruit;
+import org.acme.cxf.FruitWebService;
+
+@WebService(endpointInterface = "org.acme.cxf.FruitWebService")
+public class FruitWebServiceImpl implements FruitWebService {
+
+ private Set fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
+
+ public FruitWebServiceImpl() {
+ fruits.add(new FruitImpl("Apple", "Winter fruit"));
+ fruits.add(new FruitImpl("Pineapple", "Tropical fruit"));
+ }
+
+ @Override
+ public Set list() {
+ return fruits;
+ }
+
+ @Override
+ public Set add(Fruit fruit) {
+ fruits.add(fruit);
+ return fruits;
+ }
+
+ @Override
+ public Set delete(Fruit fruit) {
+ fruits.remove(fruit);
+ return fruits;
+ }
+}
+----
+
+The implementation is pretty straightforward and you just need to define your endpoints using the application.properties`.
+
+[source,properties]
+----
+quarkus.cxf.path=/cxf
+quarkus.cxf.webservice."/fruit"=org.acme.cxf.impl.FruitWebServiceImpl
+----
+
+
+== Conclusion
+
+Creating SOAP Web services with Quarkus is easy as it relies on proven and well known technologies.
+
+As usual, Quarkus further simplifies things under the hood when running your application as a native executable.
+
diff --git a/extensions/cxf/deployment/pom.xml b/extensions/cxf/deployment/pom.xml
new file mode 100644
index 00000000000000..8504fb04eb26cb
--- /dev/null
+++ b/extensions/cxf/deployment/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ quarkus-cxf-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-cxf-deployment
+ Quarkus - CXF - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-cxf
+
+
+ io.quarkus
+ quarkus-undertow-deployment
+
+
+ org.jboss.spec.javax.xml.ws
+ jboss-jaxws-api_2.3_spec
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ commons-io
+ commons-io
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ jakarta.jws
+ jakarta.jws-api
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
+
diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java
new file mode 100644
index 00000000000000..6efba752f1f6ac
--- /dev/null
+++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java
@@ -0,0 +1,23 @@
+package io.quarkus.cxf.deployment;
+
+import java.util.Map;
+
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot
+final class CxfConfig {
+
+ /**
+ * Set this to override the default path for JAX-RS resources if there are no
+ * annotated application classes.
+ */
+ @ConfigItem(defaultValue = "/")
+ String path;
+
+ /**
+ * Choose the path of each web services.
+ */
+ @ConfigItem(name = "webservice")
+ Map webServicesPaths;
+}
\ No newline at end of file
diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java
new file mode 100755
index 00000000000000..eab2fb42f93644
--- /dev/null
+++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java
@@ -0,0 +1,203 @@
+package io.quarkus.cxf.deployment;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
+import org.jboss.jandex.Type;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
+import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
+import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
+import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
+import io.quarkus.arc.processor.DotNames;
+import io.quarkus.cxf.runtime.AbstractCxfWebServiceProducer;
+import io.quarkus.cxf.runtime.CXFQuarkusServlet;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
+import io.quarkus.deployment.util.HashUtil;
+import io.quarkus.gizmo.ClassCreator;
+import io.quarkus.gizmo.ClassOutput;
+import io.quarkus.gizmo.MethodCreator;
+import io.quarkus.gizmo.MethodDescriptor;
+import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.undertow.deployment.ServletBuildItem;
+import io.quarkus.undertow.deployment.ServletInitParamBuildItem;
+
+/**
+ * Processor that finds JAX-RS classes in the deployment
+ */
+public class CxfProcessor {
+
+ private static final String JAX_WS_CXF_SERVLET = "org.apache.cxf.transport.servlet.CXFNonSpringServlet";
+
+ private static final DotName WEBSERVICE_ANNOTATION = DotName.createSimple("javax.jws.WebService");
+ private static final DotName ADAPTER_ANNOTATION = DotName
+ .createSimple("javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter");
+ private static final DotName UNREMOVABLE_BEAN = DotName.createSimple(AbstractCxfWebServiceProducer.class.getName());
+
+ @BuildStep
+ UnremovableBeanBuildItem markBeansAsUnremovable() {
+ return new UnremovableBeanBuildItem(beanInfo -> {
+ Set types = beanInfo.getTypes();
+ for (Type t : types) {
+ if (UNREMOVABLE_BEAN.equals(t.name())) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ /**
+ * JAX-RS configuration.
+ */
+ CxfConfig cxfConfig;
+
+ // @BuildStep
+ // void build(BuildProducer reflectiveClass,
+ // BuildProducer resourceBundle,
+ // BuildProducer serviceProvider) {
+ // reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
+ // ProviderImpl.class.getName()));
+ //
+ // serviceProvider.produce(new ServiceProviderBuildItem(Provider.class.getName(),
+ // ProviderImpl.class.getName()));
+ // }
+
+ @BuildStep
+ public void build(
+ BuildProducer reflectiveClass,
+ BuildProducer reflectiveHierarchy,
+ BuildProducer proxyDefinition,
+ BuildProducer resource,
+ BuildProducer runtimeClasses,
+ BuildProducer transformers,
+ BuildProducer cxfServerConfig,
+ BuildProducer unremovableBeans,
+ CombinedIndexBuildItem combinedIndexBuildItem,
+ BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) throws Exception {
+ IndexView index = combinedIndexBuildItem.getIndex();
+
+ for (AnnotationInstance annotation : index.getAnnotations(WEBSERVICE_ANNOTATION)) {
+ if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
+ reflectiveClass
+ .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString()));
+ }
+ }
+
+ for (AnnotationInstance annotation : index.getAnnotations(ADAPTER_ANNOTATION)) {
+ if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
+ reflectiveClass
+ .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString()));
+ }
+ AnnotationValue value = annotation.value();
+ reflectiveClass
+ .produce(new ReflectiveClassBuildItem(true, true, value.asClass().name().toString()));
+ }
+
+ Map cxfInitParameters = new HashMap<>();
+
+ cxfServerConfig.produce(new CxfServerConfigBuildItem(cxfConfig.path, cxfInitParameters));
+ }
+
+ @BuildStep
+ public void configToBuildItem(BuildProducer webServiceBuildItems) {
+ for (Entry webServicesByPath : cxfConfig.webServicesPaths.entrySet()) {
+ webServiceBuildItems.produce(new WebServiceBuildItem(webServicesByPath.getValue()));
+ }
+ }
+
+ @BuildStep
+ public void build(
+ Optional cxfServerConfig,
+ BuildProducer feature,
+ BuildProducer servlet,
+ BuildProducer reflectiveClass,
+ BuildProducer servletInitParameters) throws Exception {
+ feature.produce(new FeatureBuildItem(FeatureBuildItem.CXF));
+
+ if (cxfServerConfig.isPresent()) {
+ String path = cxfServerConfig.get().getPath();
+
+ String mappingPath = getMappingPath(path);
+ servlet.produce(ServletBuildItem.builder(JAX_WS_CXF_SERVLET, CXFQuarkusServlet.class.getName())
+ .setLoadOnStartup(1).addMapping(mappingPath).setAsyncSupported(true).build());
+ reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, CXFQuarkusServlet.class.getName()));
+
+ for (Entry initParameter : cxfServerConfig.get().getInitParameters().entrySet()) {
+ servletInitParameters.produce(new ServletInitParamBuildItem(initParameter.getKey(), initParameter.getValue()));
+ }
+
+ for (Entry webServicesByPath : cxfConfig.webServicesPaths.entrySet()) {
+ CXFQuarkusServlet.publish(webServicesByPath.getKey(), webServicesByPath.getValue());
+ }
+ }
+ }
+
+ private String getMappingPath(String path) {
+ String mappingPath;
+ if (path.endsWith("/")) {
+ mappingPath = path + "*";
+ } else {
+ mappingPath = path + "/*";
+ }
+ return mappingPath;
+ }
+
+ @BuildStep
+ public void createBeans(
+ BuildProducer unremovableBeans,
+ BuildProducer generatedBeans,
+ List webServices) throws Exception {
+ for (WebServiceBuildItem webService : webServices) {
+ ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans);
+ String webServiceName = webService.getWebServiceClass();
+ ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput)
+ .className(webServiceName + "Producer")
+ .superClass(AbstractCxfWebServiceProducer.class)
+ .build();
+ classCreator.addAnnotation(ApplicationScoped.class);
+
+ unremovableBeans.produce(new UnremovableBeanBuildItem(
+ new UnremovableBeanBuildItem.BeanClassNameExclusion(webServiceName)));
+
+ MethodCreator namedWebServiceMethodCreator = classCreator.getMethodCreator(
+ "createWebService_" + HashUtil.sha1(webServiceName),
+ webServiceName);
+ namedWebServiceMethodCreator.addAnnotation(ApplicationScoped.class);
+ namedWebServiceMethodCreator.addAnnotation(Unremovable.class);
+ namedWebServiceMethodCreator.addAnnotation(Produces.class);
+ namedWebServiceMethodCreator.addAnnotation(AnnotationInstance.create(DotNames.NAMED, null,
+ new AnnotationValue[] { AnnotationValue.createStringValue("value", webServiceName) }));
+
+ ResultHandle namedWebService = namedWebServiceMethodCreator
+ .newInstance(MethodDescriptor.ofConstructor(webServiceName));
+
+ namedWebServiceMethodCreator.returnValue(namedWebService);
+ classCreator.close();
+ }
+
+ }
+
+}
diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java
new file mode 100644
index 00000000000000..f989b8724c6c0c
--- /dev/null
+++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java
@@ -0,0 +1,28 @@
+package io.quarkus.cxf.deployment;
+
+import java.util.Map;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+/**
+ * A build item that represents the configuration of the RESTEasy server.
+ */
+public final class CxfServerConfigBuildItem extends SimpleBuildItem {
+
+ private final String path;
+
+ private final Map initParameters;
+
+ public CxfServerConfigBuildItem(String path, Map initParameters) {
+ this.path = path;
+ this.initParameters = initParameters;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Map getInitParameters() {
+ return initParameters;
+ }
+}
diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java
new file mode 100644
index 00000000000000..3d0fb318b83d0c
--- /dev/null
+++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java
@@ -0,0 +1,15 @@
+package io.quarkus.cxf.deployment;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class WebServiceBuildItem extends MultiBuildItem {
+ private final String webServiceClass;
+
+ public WebServiceBuildItem(String webServiceClass) {
+ this.webServiceClass = webServiceClass;
+ }
+
+ public String getWebServiceClass() {
+ return webServiceClass;
+ }
+}
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java
new file mode 100644
index 00000000000000..79daa30f1ba2b0
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java
@@ -0,0 +1,98 @@
+package io.quarkus.cxf.deployment.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.*;
+import javax.xml.ws.Service;
+import javax.xml.ws.soap.SOAPBinding;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import io.quarkus.test.QuarkusDevModeTest;
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+
+public class CxfServiceTest {
+
+ @RegisterExtension
+ public static final QuarkusDevModeTest test = new QuarkusDevModeTest()
+ .setArchiveProducer(new Supplier() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClass(FruitWebServiceImpl.class)
+ .addClass(FruitWebService.class)
+ .addClass(Fruit.class)
+ .addClass(FruitImpl.class)
+ .addClass(FruitAdapter.class)
+ .addAsResource("application.properties");
+ }
+ });
+
+ private static QName SERVICE_NAME = new QName("http://test.deployment.cxf.quarkus.io/", "FruitWebServiceImpl");
+ private static QName PORT_NAME = new QName("http://test.deployment.cxf.quarkus.io/", "FruitWebServiceImplPortType");
+
+ private Service service;
+ private FruitWebService fruitProxy;
+ private FruitWebServiceImpl fruitImpl;
+
+ {
+ service = Service.create(SERVICE_NAME);
+ final String endpointAddress = "http://localhost:8080/fruit";
+ service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
+ }
+
+ @BeforeEach
+ public void reinstantiateBaeldungInstances() {
+ fruitImpl = new FruitWebServiceImpl();
+ fruitProxy = service.getPort(PORT_NAME, FruitWebService.class);
+ }
+
+ @Test
+ public void whenUsingHelloMethod_thenCorrect() {
+ String xml = "\n"
+ +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+ String cnt = "";
+ try {
+ Response response = RestAssured.given().header("Content-Type", "text/xml").and().body(xml).when().post("/fruit");
+ response.then().statusCode(200);
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document doc = dBuilder.parse(response.body().asInputStream());
+ doc.getDocumentElement().normalize();
+ XPath xpath = XPathFactory.newInstance().newXPath();
+
+ cnt = xpath.compile("/Envelope/Body/countResponse/return").evaluate(doc);
+ } catch (XPathExpressionException e) {
+ e.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (SAXException e) {
+ e.printStackTrace();
+ }
+ //Assertions.assertEquals(xmlrsp, response.body().asString());
+ Assertions.assertEquals("2", cnt);
+ }
+}
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java
new file mode 100644
index 00000000000000..b5499774c362ed
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java
@@ -0,0 +1,11 @@
+package io.quarkus.cxf.deployment.test;
+
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+@XmlJavaTypeAdapter(FruitAdapter.class)
+public interface Fruit {
+
+ String getName();
+
+ String getDescription();
+}
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java
new file mode 100644
index 00000000000000..51e7cfd2fff795
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java
@@ -0,0 +1,16 @@
+package io.quarkus.cxf.deployment.test;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+public class FruitAdapter extends XmlAdapter {
+ public FruitImpl marshal(Fruit fruit) throws Exception {
+ if (fruit instanceof FruitImpl) {
+ return (FruitImpl) fruit;
+ }
+ return new FruitImpl(fruit.getName(), fruit.getDescription());
+ }
+
+ public Fruit unmarshal(FruitImpl fruit) throws Exception {
+ return fruit;
+ }
+}
\ No newline at end of file
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java
new file mode 100644
index 00000000000000..220ca5bc170245
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java
@@ -0,0 +1,53 @@
+package io.quarkus.cxf.deployment.test;
+
+import java.util.Objects;
+
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name = "Fruit")
+public class FruitImpl implements Fruit {
+
+ private String name;
+
+ private String description;
+
+ public FruitImpl() {
+ }
+
+ public FruitImpl(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Fruit)) {
+ return false;
+ }
+
+ Fruit other = (Fruit) obj;
+
+ return Objects.equals(other.getName(), this.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.getName());
+ }
+}
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java
new file mode 100644
index 00000000000000..76326e02daa33d
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java
@@ -0,0 +1,18 @@
+package io.quarkus.cxf.deployment.test;
+
+import javax.jws.WebMethod;
+import javax.jws.WebParam;
+import javax.jws.WebService;
+
+@WebService(targetNamespace = "http://test.deployment.cxf.quarkus.io/", name = "FruitWebServiceImplPortType")
+public interface FruitWebService {
+
+ @WebMethod
+ int count();
+
+ @WebMethod
+ void add(@WebParam(name = "fruit") Fruit fruit);
+
+ @WebMethod
+ void delete(@WebParam(name = "fruit") Fruit fruit);
+}
diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java
new file mode 100644
index 00000000000000..c2b596843d090c
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java
@@ -0,0 +1,34 @@
+package io.quarkus.cxf.deployment.test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import javax.jws.WebParam;
+import javax.jws.WebService;
+
+@WebService(endpointInterface = "org.acme.cxf.FruitWebService", targetNamespace = "http://test.deployment.cxf.quarkus.io/", portName = "FruitWebServiceImplPortType", serviceName = "FruitWebServiceImpl")
+public class FruitWebServiceImpl implements FruitWebService {
+
+ private Set fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
+
+ public FruitWebServiceImpl() {
+ fruits.add(new FruitImpl("Apple", "Winter fruit"));
+ fruits.add(new FruitImpl("Pineapple", "Tropical fruit"));
+ }
+
+ @Override
+ public int count() {
+ return (fruits != null ? fruits.size() : 0);
+ }
+
+ @Override
+ public void add(@WebParam(name = "fruit") Fruit fruit) {
+ fruits.add(fruit);
+ }
+
+ @Override
+ public void delete(@WebParam(name = "fruit") Fruit fruit) {
+ fruits.remove(fruit);
+ }
+}
diff --git a/extensions/cxf/deployment/src/test/resources/application.properties b/extensions/cxf/deployment/src/test/resources/application.properties
new file mode 100644
index 00000000000000..8774667780c5fe
--- /dev/null
+++ b/extensions/cxf/deployment/src/test/resources/application.properties
@@ -0,0 +1,2 @@
+quarkus.cxf.path=/
+quarkus.cxf.webservice."/fruit"=io.quarkus.cxf.deployment.test.FruitWebServiceImpl
diff --git a/extensions/cxf/pom.xml b/extensions/cxf/pom.xml
new file mode 100644
index 00000000000000..c9cb89668fa13a
--- /dev/null
+++ b/extensions/cxf/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ quarkus-build-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../../build-parent/pom.xml
+
+ 4.0.0
+
+ quarkus-cxf-parent
+ Quarkus - CXF
+ pom
+
+ deployment
+ runtime
+
+
+
diff --git a/extensions/cxf/runtime/pom.xml b/extensions/cxf/runtime/pom.xml
new file mode 100644
index 00000000000000..fbf03df0d05214
--- /dev/null
+++ b/extensions/cxf/runtime/pom.xml
@@ -0,0 +1,83 @@
+
+
+
+ quarkus-cxf-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-cxf
+ Quarkus - CXF - Runtime
+ CXF
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxws
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ javax.activation
+ javax.activation-api
+
+
+ javax.xml.soap
+ javax.xml.soap-api
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ org.apache.cxf
+ cxf-rt-transports-http
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java
new file mode 100644
index 00000000000000..bfed1009aa0cf4
--- /dev/null
+++ b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java
@@ -0,0 +1,5 @@
+package io.quarkus.cxf.runtime;
+
+public class AbstractCxfWebServiceProducer {
+ //TODO
+}
diff --git a/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java
new file mode 100644
index 00000000000000..ea13329c7760db
--- /dev/null
+++ b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java
@@ -0,0 +1,78 @@
+package io.quarkus.cxf.runtime;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletConfig;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.frontend.ServerFactoryBean;
+import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.quarkus.arc.Arc;
+
+public class CXFQuarkusServlet extends CXFNonSpringServlet {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CXFQuarkusServlet.class);
+
+ public static class WebServiceConfig {
+ private String path;
+ private String className;
+
+ public WebServiceConfig(String path, String className) {
+ super();
+ this.path = path;
+ this.className = className;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String toString() {
+ return "Web Service " + className + " on " + path;
+ }
+
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private static final List WEB_SERVICES = new ArrayList<>();
+
+ @Override
+ public void loadBus(ServletConfig servletConfig) {
+ super.loadBus(servletConfig);
+
+ // You could add the endpoint publish codes here
+ Bus bus = getBus();
+ BusFactory.setDefaultBus(bus);
+
+ // You can als use the simple frontend API to do this
+ ServerFactoryBean factory = new ServerFactoryBean();
+ factory.setBus(bus);
+
+ for (WebServiceConfig config : WEB_SERVICES) {
+ Object instanceService = Arc.container().instance(config.getClassName()).get();
+ if (instanceService != null) {
+ factory.setServiceBean(instanceService);
+ factory.setAddress(config.getPath());
+ factory.create();
+ LOGGER.info(config.toString() + " available.");
+ } else {
+ LOGGER.error("Cannot initialize " + config.toString());
+ }
+ }
+ }
+
+ public static void publish(String path, String webService) {
+ WEB_SERVICES.add(new WebServiceConfig(path, webService));
+ }
+}
diff --git a/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000000000..0efe882ba4c17b
--- /dev/null
+++ b/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,15 @@
+---
+name: "CXF"
+metadata:
+ keywords:
+ - "cxf"
+ - "jaxws"
+ - "jax-ws"
+ - "web"
+ - "soap"
+ categories:
+ - "web"
+ - "reactive"
+ guide: "https://quarkus.io/guides/cxf-guide"
+ status: "stable"
+ unlisted: "true"
diff --git a/extensions/pom.xml b/extensions/pom.xml
index bf78ad80e2d892..cbbf8cfc9c9b37 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -54,6 +54,9 @@
smallrye-openapi
swagger-ui
+
+ cxf
+
vertx
vertx-web