How To Introduce a New API Quickly Using Quarkus and ChatGPT


My last two articles (part 1 and part 2) focused on getting to market quickly using Java. The only difference was the build automation tool that I used for each example. This time, I want to step outside of my comfort zone and try something a little different.
I read about how Quarkus is a Kubernetes-native Java framework designed for building fast, lightweight microservices. What’s even better is that it is optimized for cloud environments, including features like fast startup times, low memory footprints, and support for both imperative and reactive programming models.
Like before, the key to success is how quickly we can go from idea to reality. In this case, let’s see how quickly I can establish a new API using Quarkus and deploy it to the cloud.
Leaning on ChatGPT for Assistance … Again
I started this series by asking ChatGPT to help create an OpenAPI specification for my Motivational Quotes service. This time, I’m asking ChatGPT how to get started with Quarkus for another instance of this service.
ChatGPT guides me through a series of steps, starting with using the Quarkus CLI, which I install locally with Homebrew.
$ brew install quarkusio/tap/quarkus
$ quarkus --version
3.19.4
I use the CLI to create the new project:
$ quarkus create app com.example.quotes:quotes-quarkus
The CLI responds with the following output:
Looking for the newly published extensions in registry.quarkus.io
-----------
applying codestarts...
✓ java
✓ maven
✓ quarkus
✓ config-properties
✓ tooling-dockerfiles
✓ tooling-maven-wrapper
✓ rest-codestart
-----------
[SUCCESS] ✓ quarkus project has been successfully generated in:
--> /Users/johnvester/projects/jvc/quotes-quarkus
-----------
Navigate into this directory and get started: quarkus dev
We can see from scanning the root directory that Quarkus uses the Maven build automation tool by default:
$ cd quotes-quarkus && ls -la
total 96
drwxr-xr-x 11 johnvester 352 Mar 22 11:55 .
drwxrwxrwx 91 root 2912 Mar 22 11:55 ..
-rw-r--r--@ 1 johnvester 6148 Mar 22 11:55 .DS_Store
-rw-r--r-- 1 johnvester 75 Mar 22 11:55 .dockerignore
-rw-r--r-- 1 johnvester 423 Mar 22 11:55 .gitignore
drwxr-xr-x 3 johnvester 96 Mar 22 11:55 .mvn
-rw-r--r-- 1 johnvester 1793 Mar 22 11:55 README.md
-rwxr-xr-x 1 johnvester 11172 Mar 22 11:55 mvnw
-rw-r--r-- 1 johnvester 7697 Mar 22 11:55 mvnw.cmd
-rw-r--r-- 1 johnvester 5003 Mar 22 11:55 pom.xml
drwxr-xr-x 5 johnvester 160 Mar 22 11:55 src
Like the OpenAPI specification in my first article, ChatGPT provides the necessary steps to get me to a point where I can be successful.
Developing My Quarkus Service
To avoid duplication of effort, I’ll continue to use the OpenAPI specification from my first article.
Leveraging guidance from Quarkus documentation and my prior API-First experience, I update pom.xml
as noted below:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.quotes</groupId>
<artifactId>quotes-quarkus</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<lombok.version>1.18.36</lombok.version>
<maven.compiler.release>21</maven.compiler.release>
<openapi-generator-maven-plugin.version>7.12.0</openapi-generator-maven-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.19.4</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.2</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-maven-plugin.version}</version>
<executions>
<execution>
<id>quotes-api</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/META-INF/openapi.yaml</inputSpec>
<configOptions>
<apiPackage>com.example.api</apiPackage>
<modelPackage>com.example.model</modelPackage>
</configOptions>
</configuration>
</execution>
</executions>
<configuration>
<output>${codegen.openapi.generated-sources-dir}</output>
<generatorName>jaxrs-spec</generatorName>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<generateModelDocumentation>false</generateModelDocumentation>
<generateApiDocumentation>false</generateApiDocumentation>
<configOptions>
<sourceFolder>src/main/java</sourceFolder>
<useJakartaEe>true</useJakartaEe>
<useSwaggerAnnotations>false</useSwaggerAnnotations>
<interfaceOnly>true</interfaceOnly>
<dateLibrary>java8</dateLibrary>
<openApiNullable>false</openApiNullable>
<invokerPackage>com.example.api</invokerPackage>
</configOptions>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>
This time, we place the openapi.yaml
file into the resources/META-INF
folder and create an application.properties
file in the resources
folder. The contents of the file are as follows:
quarkus.banner.path=banner.txt
quarkus.http.port=8080
mp.openapi.scan.disable=true
Our banner.txt
file has the following contents:
_
__ _ _ _ ___ | |_ ___ ___
/ _` | | | |/ _ \| __/ _ \/ __|
| (_| | |_| | (_) | || __/\__ \
\__, |\__,_|\___/ \__\___||___/
|_|
Create the Business Logic
Like in my prior articles, we’ll use in-memory data for our motivational quotes. In a newly-created repositories
package, I add the QuotesRepository
class, which is very similar to what we’ve used so far:
@ApplicationScoped
public class QuotesRepository {
public static final List<Quote> QUOTES = List.of(
new Quote()
.id(1)
.quote("The greatest glory in living lies not in never falling, but in rising every time we fall."),
new Quote()
.id(2)
.quote("The way to get started is to quit talking and begin doing."),
new Quote()
.id(3)
.quote("Your time is limited, so don't waste it living someone else's life."),
new Quote()
.id(4)
.quote("If life were predictable it would cease to be life, and be without flavor."),
new Quote()
.id(5)
.quote("If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success.")
);
public List<Quote> getAllQuotes() {
return QUOTES;
}
public Optional<Quote> getQuoteById(Integer id) {
return Optional.ofNullable(QUOTES.stream().filter(quote -> quote.getId().equals(id)).findFirst().orElse(null));
}
}
Next, I add the following QuotesService
—which calls the QuotesRepository
—to a newly-created services
package.
@RequiredArgsConstructor
@ApplicationScoped
public class QuotesService {
private final QuotesRepository quotesRepository;
public List<Quote> getAllQuotes() {
return quotesRepository.getAllQuotes();
}
public Optional<Quote> getQuoteById(Integer id) {
return quotesRepository.getQuoteById(id);
}
public Quote getRandomQuote() {
List<Quote> quotes = quotesRepository.getAllQuotes();
return quotes.get(ThreadLocalRandom.current().nextInt(quotes.size()));
}
}
Finally, I implement the QuotesApi
created by our API-First approach. I create a QuotesApiImpl
class in a newly-created controllers
package with the following contents:
@RequiredArgsConstructor
public class QuotesApiImpl implements QuotesApi {
private final QuotesService quotesService;
@Override
public List<Quote> getAllQuotes() {
return quotesService.getAllQuotes();
}
@Override
public Quote getQuoteById(Integer id) {
return quotesService.getQuoteById(id)
.orElseThrow(() -> new NotFoundException("Quote not found for id: " + id));
}
@Override
public Quote getRandomQuote() {
return quotesService.getRandomQuote();
}
}
We can add a controller test by creating the QuotesApiResourceTest
in a newly-created controllers
test package:
@QuarkusTest
class QuotesApiResourceTest {
@Test
void testGetAllQuotes() {
given()
.when().get("/quotes")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("$.size()", is(5));
}
@Test
void testGetQuoteById() {
given()
.when().get("/quotes/1")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", is(1));
}
@Test
void testGetRandomQuote() {
given()
.when().get("/quotes/random")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", isA(Integer.class));
}
}
We also add the QuotesApiResourceIT
integration test, which simply calls the test above:
@QuarkusIntegrationTest
class QuotesApiResourceIT extends QuotesApiResourceTest {
// Integration Tests ran in packaged mode
}
Now we are ready to run our service.
Building and Running the Service
We use the following command to start a local instance of our API:
$ quarkus dev
The command builds the project, runs the tests, and starts a local instance:
Listening for transport dt_socket at address: 5005
_
__ _ _ _ ___ | |_ ___ ___
/ _` | | | |/ _ \| __/ _ \/ __|
| (_| | |_| | (_) | || __/\__ \
\__, |\__,_|\___/ \__\___||___/
|_|
Powered by Quarkus 3.19.4
2025-03-30 12:51:38,235 INFO [io.quarkus] (Quarkus Main Thread) quotes-quarkus 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.19.4) started in 3.729s. Listening on: http://localhost:8080
2025-03-30 12:51:38,259 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-03-30 12:51:38,261 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-openapi, swagger-ui, vertx]
--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>
We can validate that the service is working locally with the following curl command:
curl --location 'http://localhost:8080/quotes/random'
{
"id": 2,
"quote": "The way to get started is to quit talking and begin doing."
}
Everything looks good!
The Dev UI in Quarkus
When running in local dev mode, Quarkus provides a really nice developer UI:
The Endpoints option provides both RESTful URLs and additional URLs to help ease the development process:
We can also use the http://localhost:8080/q/swagger-ui/ URL to view the Swagger UI:
We can stop the local instance with Ctrl-C
.
Leveraging Heroku to Deploy the Service
Since I used Heroku for my prior articles, I wonder if support exists for Quarkus services. It turns out… it does! Going with Heroku helps me deploy my services quickly. I don’t lose time dealing with infrastructure concerns.
Like before, I need to allow for the service port to be overridden. In this case, we just need to update the following line in application.properties
, as PORT
will be set by Heroku at runtime:
quarkus.http.port=${PORT:8080}
To match the Java version we’re using, we create a system.properties
file in the root folder of the project. The file has one line:
java.runtime.version = 17
Then, I create a Procfile
in the same location for customizing the deployment behavior. This file also has one line:
web: java \$JAVA_OPTS -jar target/quarkus-app/quarkus-run.jar
Next we need to execute the mvn package
command as a final step before we attempt to deploy to Heroku. Expanding the target folder in IntelliJ shows the quarkus-run.jar
file that is expected by the Procfile
(above).
It’s time to deploy. With the Heroku CLI, I can deploy the service using a few simple commands. First, I authenticate the CLI and then create a new Heroku app.
$ heroku login
$ heroku create
The CLI responds with the following response:
Creating app... done, ⬢ murmuring-refuge-95709
https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/ | https://git.heroku.com/murmuring-refuge-95709.git
My Heroku app instance is named murmuring-refuge-95709
, so my service will run at https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/.
One last thing to do … push the code to Heroku, which deploys the service:
$ git push heroku main
Switching back to Heroku Dashboard, we see our service has deployed successfully:
But Wait … There’s More
In addition to the JAR-based approach, Heroku supports deploying Quarkus services using a Docker or Podman container. Taking this approach provides complete control over its content, allowing deployment via a native executable running on GraalVM.
See this guide for additional details.
Motivational Quotes in Action
Using the Heroku app URL, https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/, we can now test our Motivational Quotes API using curl commands.
First, we retrieve the list of quotes:
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes'
[
{
"id": 1,
"quote": "The greatest glory in living lies not in never falling, but in rising every time we fall."
},
{
"id": 2,
"quote": "The way to get started is to quit talking and begin doing."
},
{
"id": 3,
"quote": "Your time is limited, so don't waste it living someone else's life."
},
{
"id": 4,
"quote": "If life were predictable it would cease to be life, and be without flavor."
},
{
"id": 5,
"quote": "If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."
}
]
We can retrieve a single quote by its ID:
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes/3'
{
"id": 3,
"quote": "Your time is limited, so don't waste it living someone else's life."
}
We can retrieve a random motivational quote:
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes/random'
{
"id": 2,
"quote": "The way to get started is to quit talking and begin doing."
}
We can view the Heroku Dashboard again to see our metrics after running these commands:
Conclusion
My readers may recall my personal mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.” — J. Vester
In this article, I had to step outside my comfort zone and work with Quarkus for the very first time. I was able to ease the learning curve by leveraging ChatGPT. Once the project was created and I saw the build system being used was Maven, I was able to use my existing skills to get the API-First functionality in place.
From there, I was able to reach out to ChatGPT again to ask how I could convert my existing Spring Boot code to work with Quarkus. The ensuing response introduced me to the @ApplicationScoped
annotation used by Quarkus. Quarkus provided a CLI that helped me build and run the service locally, while also providing a Dev UI to ease the process to learn this new service option.
From a Heroku side, I was able to quickly deploy and validate my service using the same approach I have followed before. This saved me time in trying to figure out something new when my preference is to focus on making my service better.
For these reasons, ChatGPT, Quarkus, and Heroku all adhere to my mission statement. They helped me deploy the new API quickly and without a huge learning burden.
If you are interested in the source code for this article, it is available on GitLab.
Have a really great day!
Subscribe to my newsletter
Read articles from John Vester directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

John Vester
John Vester
Information Technology professional with 30+ years expertise in application design and architecture, feature development, project management, system administration and team supervision. Currently focusing on enterprise architecture/application design utilizing object-oriented programming languages and frameworks.