You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/10/09 16:39:40 UTC

[karaf] branch main updated: [KARAF-7567] Add GraphQL example

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/main by this push:
     new 431b3920c9 [KARAF-7567] Add GraphQL example
     new c1b4e670d7 This closes #1631
431b3920c9 is described below

commit 431b3920c9281526997070695802a716c517bac8
Author: Aleksy Wróblewski <al...@bbbit.io>
AuthorDate: Wed Oct 5 22:54:37 2022 +0200

    [KARAF-7567] Add GraphQL example
---
 examples/karaf-graphql-example/README.md           |  98 ++++++++++++++
 .../karaf-graphql-example-api/pom.xml              |  43 ++++++
 .../apache/karaf/examples/graphql/api/Book.java    |  54 ++++++++
 .../karaf/examples/graphql/api/BookRepository.java |  28 ++++
 .../graphql/api/GraphQLSchemaProvider.java         |  24 ++++
 .../karaf-graphql-example-commands/pom.xml         |  78 +++++++++++
 .../karaf/examples/graphql/commands/Query.java     |  56 ++++++++
 .../karaf-graphql-example-core/pom.xml             |  59 +++++++++
 .../examples/graphql/core/BookSchemaProvider.java  | 103 +++++++++++++++
 .../graphql/core/InMemoryBookRepository.java       |  63 +++++++++
 .../src/main/resources/schema.graphql              |  41 ++++++
 .../karaf-graphql-example-features/pom.xml         |  79 +++++++++++
 .../src/main/feature/feature.xml                   |  53 ++++++++
 .../karaf-graphql-example-scr-servlet/pom.xml      |  75 +++++++++++
 .../graphql/servlet/ExampleGraphQLHttpServlet.java |  41 ++++++
 .../karaf-graphql-example-websocket/pom.xml        |  73 ++++++++++
 .../graphql/websocket/GraphQLWebSocketExample.java |  92 +++++++++++++
 .../graphql/websocket/GraphQLWebSocketServlet.java |  49 +++++++
 examples/karaf-graphql-example/pom.xml             |  44 ++++++
 examples/pom.xml                                   |   1 +
 .../karaf/itests/examples/GraphQLExampleTest.java  | 147 +++++++++++++++++++++
 .../itests/examples/WebSocketExampleTest.java      |  50 +------
 .../org/apache/karaf/itests/util/SimpleSocket.java |  66 +++++++++
 23 files changed, 1368 insertions(+), 49 deletions(-)

diff --git a/examples/karaf-graphql-example/README.md b/examples/karaf-graphql-example/README.md
new file mode 100644
index 0000000000..0019b924ae
--- /dev/null
+++ b/examples/karaf-graphql-example/README.md
@@ -0,0 +1,98 @@
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+# Apache Karaf GraphQL example
+
+## Abstract
+
+This example shows how to use GraphQL in Karaf. 
+We demonstrate how to use GraphQL with WebSockets, with a HTTP servlet and with Karaf commands.
+
+## Build
+The build uses Apache Maven. Simply use:
+
+```
+mvn clean install
+```
+
+## Deployment
+
+On a running Karaf instance, add a feature repository and then the feature:
+```
+karaf@root()> feature:repo-add mvn:org.apache.karaf.examples/karaf-graphql-example-features/LATEST/xml
+karaf@root()> feature:install karaf-graphql-example 
+```
+
+## Usage
+A HTTP server will start on the configured port in Karaf (8181 by default).
+The following endpoints can be used to test the GraphQL API:
+```
+GET http://localhost:8181/graphql?query={ bookById(id:"1") { name }}
+GET http://localhost:8181/graphql?query={ bookById(id:"2") { name  id authorId pageCount}}
+
+POST http://localhost:8181/graphql
+{
+  "query": "mutation { addBook(name:\"Test\", pageCount:100) { name } }"
+}
+```
+
+Additionally, a `graphql:query` command will be available. It takes a single mandatory argument
+which needs to be a valid GraphQL query in the defined GraphQL schema.
+For instance:
+```
+karaf@root()> graphql:query "{books { name id pageCount }}"
+{books=[{name=Apache Karaf Cookbook, id=1, pageCount=260}, {name=Effective Java, id=2, pageCount=416}, {name=OSGi in Action, id=3, pageCount=375}]}
+
+karaf@root()> graphql:query "{bookById(id:1) { name id pageCount }}"                                                                                                                  
+{bookById={name=Apache Karaf Cookbook, id=1, pageCount=260}}
+
+karaf@root()> graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name }}"
+{addBook={id=9, name=Lord of the Rings}}
+```
+
+Finally, the `karaf-graphql-example-websocket` bundle contains a WebSocket endpoint that will publish updates
+when new data is added via GraphQL. To test, execute the following cURL command:
+```
+curl --include \
+     --no-buffer \
+     --header "Connection: Upgrade" \
+     --header "Upgrade: websocket" \
+     --header "Host: localhost:8181" \
+     --header "Origin: http://localhost:8181/graphql-websocket" \
+     --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
+     --header "Sec-WebSocket-Version: 13" \
+     http://localhost:8181/graphql-websocket
+```
+You should see a similar response:
+```
+HTTP/1.1 101 Switching Protocols
+Date: Tue, 04 Oct 2022 21:07:55 GMT
+Connection: Upgrade
+Sec-WebSocket-Accept: qGEgH3En71di5rrssAZTmtRTyFk=
+Upgrade: WebSocket
+
+```
+
+Add a new book by with the GraphQL mutation (either with a POST request or Karaf command):
+```
+karaf@root()> graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:123) { id name }}"                                                                               
+```
+and observe the update come in real time to the terminal with cURL:
+```
+{bookCreated={id=6, name=Lord of the Rings}}
+```
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml
new file mode 100644
index 0000000000..10435c2c0c
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.examples</groupId>
+        <artifactId>karaf-graphql-example</artifactId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-api</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: API</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.graphql-java</groupId>
+            <artifactId>graphql-java</artifactId>
+            <version>19.2</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java
new file mode 100644
index 0000000000..141f7d69df
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.api;
+
+public class Book {
+
+    private final String name;
+    private final int pageCount;
+    private String id;
+
+    public Book(String name, int pageCount) {
+        this.name = name;
+        this.pageCount = pageCount;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPageCount() {
+        return pageCount;
+    }
+
+    @Override
+    public String toString() {
+        return "Book{" +
+                "id='" + id + '\'' +
+                ", name='" + name + '\'' +
+                ", pageCount=" + pageCount +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java
new file mode 100644
index 0000000000..d73355d0fa
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.api;
+
+import java.util.Collection;
+
+public interface BookRepository {
+
+    Book storeBook(Book book);
+
+    Collection<Book> getBooks();
+
+    Book getBookById(String id);
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java
new file mode 100644
index 0000000000..8978ec5f3f
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.api;
+
+import graphql.schema.GraphQLSchema;
+
+public interface GraphQLSchemaProvider {
+
+    GraphQLSchema createSchema();
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml
new file mode 100644
index 0000000000..be58b084ae
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.examples</groupId>
+        <artifactId>karaf-graphql-example</artifactId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-commands</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: Shell Commands</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.graphql-java</groupId>
+            <artifactId>graphql-java</artifactId>
+            <version>19.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.examples</groupId>
+            <artifactId>karaf-graphql-example-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.annotation</artifactId>
+            <version>8.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Karaf-Commands>*</Karaf-Commands>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java b/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java
new file mode 100644
index 0000000000..683409874f
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.commands;
+
+import graphql.ExecutionResult;
+import graphql.GraphQL;
+import graphql.schema.GraphQLSchema;
+import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+/**
+ * Sample calls:
+ * graphql:query "{books { name }}"
+ * graphql:query "{bookById(id:\"1\") { name id pageCount }}"
+ * graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name }}
+ */
+@Service
+@Command(scope = "graphql", name = "query", description = "Execute GraphQL query")
+public class Query implements Action {
+
+    @Argument(index = 0, name = "query", required = true, multiValued = false)
+    String query;
+
+    @Reference
+    private GraphQLSchemaProvider schemaProvider;
+
+    @Override
+    public Object execute() throws Exception {
+        GraphQLSchema schema = schemaProvider.createSchema();
+        ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(query);
+        if (result.getData() != null) {
+            System.out.println(result.getData().toString());
+        } else {
+            result.getErrors().forEach(System.out::println);
+        }
+        return null;
+    }
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml
new file mode 100644
index 0000000000..7d10181711
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>karaf-graphql-example</artifactId>
+        <groupId>org.apache.karaf.examples</groupId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-core</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: Core</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.examples</groupId>
+            <artifactId>karaf-graphql-example-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.graphql-java</groupId>
+            <artifactId>graphql-java</artifactId>
+            <version>19.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.reactivex.rxjava3</groupId>
+            <artifactId>rxjava</artifactId>
+            <version>3.1.5</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java
new file mode 100644
index 0000000000..8050ac198e
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java
@@ -0,0 +1,103 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.core;
+
+import graphql.schema.DataFetcher;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.idl.RuntimeWiring;
+import graphql.schema.idl.SchemaGenerator;
+import graphql.schema.idl.SchemaParser;
+import graphql.schema.idl.TypeDefinitionRegistry;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
+import io.reactivex.rxjava3.observables.ConnectableObservable;
+import io.reactivex.rxjava3.subjects.PublishSubject;
+import org.apache.karaf.examples.graphql.api.Book;
+import org.apache.karaf.examples.graphql.api.BookRepository;
+import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.reactivestreams.Publisher;
+
+import java.util.Collection;
+
+@Component(service = GraphQLSchemaProvider.class)
+public class BookSchemaProvider implements GraphQLSchemaProvider {
+
+    @Reference(service = BookRepository.class)
+    private BookRepository bookRepository;
+
+    private PublishSubject<Book> subject;
+
+    public void setBookRepository(InMemoryBookRepository bookRepository) {
+        this.bookRepository = bookRepository;
+    }
+
+    @Activate
+    public void activate() {
+        subject = PublishSubject.create();
+    }
+
+    @Override
+    public GraphQLSchema createSchema() {
+        SchemaParser schemaParser = new SchemaParser();
+        TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(
+                this.getClass().getResourceAsStream("/schema.graphql"));
+
+        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
+                .type("Mutation", builder -> builder.dataFetcher("addBook", addBookFetcher()))
+                .type("Query", builder -> builder.dataFetcher("bookById", bookByIdFetcher()))
+                .type("Query", builder -> builder.dataFetcher("books", booksFetcher()))
+                .type("Subscription", builder -> builder.dataFetcher("bookCreated", bookCreatedFetcher()))
+                .build();
+
+        SchemaGenerator schemaGenerator = new SchemaGenerator();
+        return schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
+    }
+
+    private DataFetcher<Book> addBookFetcher() {
+        return environment -> {
+            String name = environment.getArgument("name");
+            int pageCount = environment.getArgument("pageCount");
+            Book book = bookRepository.storeBook(new Book(name, pageCount));
+            subject.onNext(book);
+            return book;
+        };
+    }
+
+    private DataFetcher<Book> bookByIdFetcher() {
+        return environment -> {
+            String id = environment.getArgument("id");
+            return bookRepository.getBookById(id);
+        };
+    }
+
+    private DataFetcher<Collection<Book>> booksFetcher() {
+        return environment -> bookRepository.getBooks();
+    }
+
+    private DataFetcher<Publisher<Book>> bookCreatedFetcher() {
+        return environment -> getPublisher();
+    }
+
+    private Publisher<Book> getPublisher() {
+        subject = PublishSubject.create();
+        ConnectableObservable<Book> connectableObservable = subject.share().publish();
+        connectableObservable.connect();
+        return connectableObservable.toFlowable(BackpressureStrategy.BUFFER);
+    }
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java
new file mode 100644
index 0000000000..b35d7b127a
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.core;
+
+import org.apache.karaf.examples.graphql.api.Book;
+import org.apache.karaf.examples.graphql.api.BookRepository;
+import org.osgi.service.component.annotations.Component;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component(service = BookRepository.class)
+public class InMemoryBookRepository implements BookRepository {
+
+    private final AtomicInteger idCounter = new AtomicInteger(0);
+    private final Map<String, Book> booksById = new HashMap<>();
+
+    public void activate() {
+        createInitialBooks();
+    }
+
+    @Override
+    public Book storeBook(Book book) {
+        String id = nextId();
+        book.setId(id);
+        booksById.put(id, book);
+        return book;
+    }
+
+    public Collection<Book> getBooks() {
+        return booksById.values();
+    }
+
+    public Book getBookById(String id) {
+        return booksById.get(id);
+    }
+
+    private void createInitialBooks() {
+        storeBook(new Book("Apache Karaf Cookbook", 260));
+        storeBook(new Book("Effective Java", 416));
+        storeBook(new Book("OSGi in Action", 375));
+    }
+
+    private String nextId() {
+        return Integer.toString(idCounter.incrementAndGet());
+    }
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql
new file mode 100644
index 0000000000..d70b6c7c46
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql
@@ -0,0 +1,41 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+schema {
+  query: Query
+  mutation: Mutation
+  subscription: Subscription
+}
+
+type Query {
+  bookById(id: ID): Book
+  books: [Book]
+}
+
+type Book {
+  id: ID
+  name: String
+  pageCount: Int
+}
+
+type Subscription {
+  bookCreated: Book
+}
+
+type Mutation {
+    addBook(name: String, pageCount: Int): Book
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml
new file mode 100644
index 0000000000..e25741b9ec
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>karaf-graphql-example</artifactId>
+        <groupId>org.apache.karaf.examples</groupId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-features</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: Features</name>
+    <packaging>pom</packaging>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/feature</directory>
+                <filtering>true</filtering>
+                <targetPath>${project.build.directory}/feature</targetPath>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>resources</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-artifacts</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attach-artifact</goal>
+                        </goals>
+                        <configuration>
+                            <artifacts>
+                                <artifact>
+                                    <file>target/feature/feature.xml</file>
+                                    <type>xml</type>
+                                </artifact>
+                            </artifacts>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml b/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml
new file mode 100644
index 0000000000..fa70ccd9fd
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<features name="karaf-graphql-example" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.4.0">
+
+    <feature name="karaf-graphql-example" version="${project.version}">
+        <feature prerequisite="true">scr</feature>
+        <feature prerequisite="true">http-whiteboard</feature>
+        <feature prerequisite="true">http</feature>
+        <feature prerequisite="true">wrap</feature>
+        <feature prerequisite="true">pax-web-karaf</feature>
+        <feature prerequisite="true">pax-web-jetty-websockets</feature>
+
+        <bundle dependency="true">wrap:mvn:com.graphql-java/java-dataloader/3.2.0</bundle>
+        <bundle dependency="true">mvn:org.reactivestreams/reactive-streams/1.0.4</bundle>
+        <bundle dependency="true">wrap:mvn:com.graphql-java/graphql-java/19.2</bundle>
+
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.datatype/jackson-datatype-jdk8/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:javax.servlet/javax.servlet-api/4.0.1</bundle>
+        <bundle dependency="true">mvn:javax.websocket/javax.websocket-api/1.1</bundle>
+
+        <bundle dependency="true">mvn:com.graphql-java-kickstart/graphql-java-kickstart/14.0.0</bundle>
+        <bundle dependency="true">mvn:com.graphql-java-kickstart/graphql-java-servlet/14.0.0</bundle>
+
+        <bundle dependency="true">mvn:io.reactivex.rxjava3/rxjava/3.1.5</bundle>
+        <bundle dependency="true">mvn:org.eclipse.jetty.websocket/websocket-server/9.4.48.v20220622</bundle>
+
+        <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-api/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-core/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-scr-servlet/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-commands/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-websocket/${project.version}</bundle>
+    </feature>
+
+</features>
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml
new file mode 100644
index 0000000000..5499b8a6fd
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>karaf-graphql-example</artifactId>
+        <groupId>org.apache.karaf.examples</groupId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-scr-servlet</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: SCR Servlet</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.graphql-java</groupId>
+            <artifactId>graphql-java</artifactId>
+            <version>19.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.graphql-java-kickstart</groupId>
+            <artifactId>graphql-java-servlet</artifactId>
+            <version>14.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>4.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.examples</groupId>
+            <artifactId>karaf-graphql-example-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java
new file mode 100644
index 0000000000..c53ab828d5
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.examples.graphql.servlet;
+
+import graphql.kickstart.servlet.GraphQLConfiguration;
+import graphql.kickstart.servlet.GraphQLHttpServlet;
+import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import javax.servlet.Servlet;
+
+@Component(service = Servlet.class, property = {"alias=/graphql", "servlet-name=GraphQL"})
+public class ExampleGraphQLHttpServlet extends GraphQLHttpServlet {
+
+    @Reference
+    private GraphQLSchemaProvider schemaProvider;
+
+    public void setSchemaProvider(GraphQLSchemaProvider schemaProvider) {
+        this.schemaProvider = schemaProvider;
+    }
+
+    @Override
+    protected GraphQLConfiguration getConfiguration() {
+        return GraphQLConfiguration.with(schemaProvider.createSchema()).build();
+    }
+}
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml
new file mode 100644
index 0000000000..ddbddc0a42
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>karaf-graphql-example</artifactId>
+        <groupId>org.apache.karaf.examples</groupId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example-websocket</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL :: WebSocket</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-servlet</artifactId>
+            <version>9.4.48.v20220622</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.examples</groupId>
+            <artifactId>karaf-graphql-example-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>11</source>
+                    <target>11</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java
new file mode 100644
index 0000000000..ce2fe6eea1
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java
@@ -0,0 +1,92 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.karaf.examples.graphql.websocket;
+
+import graphql.ExecutionResult;
+import graphql.GraphQL;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+@WebSocket
+public class GraphQLWebSocketExample {
+
+    private static final List<Session> sessions = new ArrayList<>();
+    private final GraphQL graphQL;
+
+    public GraphQLWebSocketExample(GraphQL graphQL) {
+        this.graphQL = graphQL;
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session session) {
+        session.setIdleTimeout(-1);
+        sessions.add(session);
+
+        String query = "" +
+                "    subscription BookFeed {\n" +
+                "        bookCreated {\n" +
+                "            id\n" +
+                "            name\n" +
+                "        }\n" +
+                "    }\n";
+
+        ExecutionResult executionResult = graphQL.execute(query);
+        Publisher<ExecutionResult> bookStream = executionResult.getData();
+        AtomicReference<Subscription> subscriptionRef = new AtomicReference<>();
+        bookStream.subscribe(new Subscriber<>() {
+            @Override
+            public void onSubscribe(Subscription subscription) {
+                subscriptionRef.set(subscription);
+                subscription.request(1);
+            }
+
+            @Override
+            public void onNext(ExecutionResult executionResult) {
+                try {
+                    session.getRemote().sendString(executionResult.getData().toString());
+                    subscriptionRef.get().request(1);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+    }
+
+    @OnWebSocketClose
+    public void onClose(Session session, int statusCode, String reason) {
+        sessions.remove(session);
+    }
+}
\ No newline at end of file
diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java
new file mode 100644
index 0000000000..ff126b3bc2
--- /dev/null
+++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.karaf.examples.graphql.websocket;
+
+import graphql.GraphQL;
+import graphql.execution.SubscriptionExecutionStrategy;
+import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider;
+import org.eclipse.jetty.websocket.servlet.*;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import javax.servlet.Servlet;
+import javax.servlet.annotation.WebServlet;
+
+@WebServlet(name = "Example GraphQL WebSocket Servlet", urlPatterns = {"/graphql-websocket"})
+@Component(service = Servlet.class, property = {"osgi.http.whiteboard.servlet.pattern=/graphql-websocket"})
+public class GraphQLWebSocketServlet extends WebSocketServlet implements WebSocketCreator {
+
+    @Reference(service = GraphQLSchemaProvider.class)
+    private GraphQLSchemaProvider schemaProvider;
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) {
+        GraphQL graphQL = GraphQL.newGraphQL(schemaProvider.createSchema())
+                .subscriptionExecutionStrategy(new SubscriptionExecutionStrategy())
+                .build();
+        return new GraphQLWebSocketExample(graphQL);
+    }
+
+    @Override
+    public void configure(WebSocketServletFactory factory) {
+        factory.setCreator(this);
+    }
+}
diff --git a/examples/karaf-graphql-example/pom.xml b/examples/karaf-graphql-example/pom.xml
new file mode 100644
index 0000000000..e0348dfd45
--- /dev/null
+++ b/examples/karaf-graphql-example/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.examples</groupId>
+        <artifactId>apache-karaf-examples</artifactId>
+        <version>4.4.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>karaf-graphql-example</artifactId>
+    <name>Apache Karaf :: Examples :: GraphQL Example</name>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>karaf-graphql-example-scr-servlet</module>
+        <module>karaf-graphql-example-commands</module>
+        <module>karaf-graphql-example-core</module>
+        <module>karaf-graphql-example-api</module>
+        <module>karaf-graphql-example-websocket</module>
+        <module>karaf-graphql-example-features</module>
+    </modules>
+
+</project>
diff --git a/examples/pom.xml b/examples/pom.xml
index f683278fa3..73c183a67f 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -67,6 +67,7 @@
         <module>karaf-soap-example</module>
         <module>karaf-websocket-example</module>
         <module>karaf-war-example</module>
+        <module>karaf-graphql-example</module>
     </modules>
 
 </project>
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java b/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java
new file mode 100644
index 0000000000..d41d994cc4
--- /dev/null
+++ b/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.itests.examples;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.itests.BaseTest;
+import org.apache.karaf.itests.util.SimpleSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerMethod;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class GraphQLExampleTest extends BaseTest {
+
+    private void setUp() throws Exception {
+        addFeaturesRepository("mvn:org.apache.karaf.examples/karaf-graphql-example-features/" + System.getProperty("karaf.version") + "/xml");
+        installAndAssertFeature("karaf-graphql-example");
+    }
+
+    @Test
+    public void testServlet() throws Exception {
+        setUp();
+
+        String getBooksQuery = "{ books { name id } }";
+        String booksRequestResult = sendGetRequest(getBooksQuery);
+
+        assertContains("\"name\":\"Apache Karaf Cookbook\"", booksRequestResult);
+        assertContains("\"id\":\"1\"", booksRequestResult);
+        assertContains("\"name\":\"Effective Java\"", booksRequestResult);
+        assertContains("\"id\":\"2\"", booksRequestResult);
+
+        String addBookQuery = "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name } }";
+        String addBookRequestResult = sendPostRequest(addBookQuery);
+        assertContains("id", addBookRequestResult);
+        assertContains("Lord of the Rings", addBookRequestResult);
+
+        String getBooksByIdQuery = "{ bookById(id:4) { name } }";
+        String getBookByIdRequestResult = sendGetRequest(getBooksByIdQuery);
+        assertContains("Lord of the Rings", getBookByIdRequestResult);
+    }
+
+    @Test
+    public void testCommand() throws Exception {
+        setUp();
+        String getBooksQuery = "\"{ books { name id } }\"";
+        String output = executeCommand("graphql:query " + getBooksQuery);
+        System.out.println(output);
+    }
+
+    @Test
+    public void testWebSocket() throws Exception {
+        setUp();
+
+        WebSocketClient client = new WebSocketClient();
+        SimpleSocket socket = new SimpleSocket();
+        client.start();
+        URI uri = new URI("ws://localhost:" + getHttpPort() + "/graphql-websocket");
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        client.connect(socket, uri, request);
+
+        sendPostRequest("mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name } }");
+
+        socket.awaitClose(10, TimeUnit.SECONDS);
+
+        assertTrue(socket.messages.size() > 0);
+
+        assertContains("Lord of the Rings", socket.messages.get(0));
+
+        client.stop();
+    }
+
+    private String sendGetRequest(String query) throws Exception {
+        String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8.name());
+        URL url = new URL("http://localhost:" + getHttpPort() + "/graphql?query=" + encodedQuery);
+
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("GET");
+        connection.setDoInput(true);
+        connection.setRequestProperty("Accept", "application/json");
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        String line;
+        StringBuilder buffer = new StringBuilder();
+        while ((line = reader.readLine()) != null) {
+            buffer.append(line);
+        }
+
+        return buffer.toString();
+    }
+
+    private String sendPostRequest(String query) throws Exception {
+        URL url = new URL("http://localhost:" + getHttpPort() + "/graphql");
+
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setDoOutput(true);
+        connection.setRequestProperty("Content-Type", "application/json");
+        connection.setRequestProperty("Accept", "application/json");
+
+        ObjectNode node = JsonNodeFactory.instance.objectNode();
+        node.put("query", query);
+
+        try (OutputStream os = connection.getOutputStream()) {
+            byte[] input = node.toString().getBytes(StandardCharsets.UTF_8.name());
+            os.write(input, 0, input.length);
+        }
+
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        String line;
+        StringBuilder buffer = new StringBuilder();
+        while ((line = reader.readLine()) != null) {
+            buffer.append(line);
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java b/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java
index 8f36ca012e..0c77511f1f 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java
@@ -17,12 +17,7 @@
 package org.apache.karaf.itests.examples;
 
 import org.apache.karaf.itests.BaseTest;
-import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
-import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.apache.karaf.itests.util.SimpleSocket;
 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
 import org.junit.Test;
@@ -33,9 +28,6 @@ import org.ops4j.pax.exam.spi.reactors.PerClass;
 import org.osgi.framework.Bundle;
 
 import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 import static junit.framework.TestCase.assertEquals;
@@ -77,44 +69,4 @@ public class WebSocketExampleTest extends BaseTest {
 
         client.stop();
     }
-
-    @WebSocket
-    public class SimpleSocket {
-
-        private final CountDownLatch closeLatch;
-
-        private Session session;
-
-        public final List<String> messages;
-
-        public SimpleSocket() {
-            this.messages = new ArrayList<>();
-            this.closeLatch = new CountDownLatch(1);
-        }
-
-        public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException {
-            return this.closeLatch.await(duration, unit);
-        }
-
-        @OnWebSocketClose
-        public void onClose(int statusCode, String reason) {
-            System.out.println("Closing websocket client");
-            session.close(StatusCode.NORMAL, "I'm done");
-            this.session = null;
-            this.closeLatch.countDown(); // trigger latch
-        }
-
-        @OnWebSocketConnect
-        public void onConnect(Session session) {
-            System.out.println("Connecting websocket client");
-            this.session = session;
-        }
-
-        @OnWebSocketMessage
-        public void onMessage(String msg) {
-            System.out.println("Received websocket message: " + msg);
-            messages.add(msg);
-        }
-    }
-
 }
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java b/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java
new file mode 100644
index 0000000000..adc930f657
--- /dev/null
+++ b/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.itests.util;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@WebSocket
+public class SimpleSocket {
+
+    public final List<String> messages;
+    private final CountDownLatch closeLatch;
+    private Session session;
+
+    public SimpleSocket() {
+        this.messages = new ArrayList<>();
+        this.closeLatch = new CountDownLatch(1);
+    }
+
+    public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException {
+        return this.closeLatch.await(duration, unit);
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason) {
+        System.out.println("Closing websocket client");
+        session.close(StatusCode.NORMAL, "I'm done");
+        this.session = null;
+        this.closeLatch.countDown(); // trigger latch
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session session) {
+        System.out.println("Connecting websocket client");
+        this.session = session;
+    }
+
+    @OnWebSocketMessage
+    public void onMessage(String msg) {
+        System.out.println("Received websocket message: " + msg);
+        messages.add(msg);
+    }
+}