DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Build a REST API With Just 2 Classes in Java and Quarkus
  • Beyond Simple Responses: Building Truly Conversational LLM Chatbots
  • Multi-Tenancy and Its Improved Support in Hibernate 6.3.0
  • Model-Driven Development and Testing

Trending

  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • AWS to Azure Migration: A Cloudy Journey of Challenges and Triumphs
  1. DZone
  2. Coding
  3. Frameworks
  4. How To Introduce a New API Quickly Using Quarkus and ChatGPT

How To Introduce a New API Quickly Using Quarkus and ChatGPT

Stepping outside of my comfort zone, let’s see how quickly we can introduce an API-First-driven RESTful by leveraging ChatGPT, Quarkus, and Heroku.

By 
John Vester user avatar
John Vester
DZone Core CORE ·
May. 21, 25 · Analysis
Likes (2)
Comment
Save
Tweet
Share
8.8K Views

Join the DZone community and get the full member experience.

Join For Free

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.

Shell
 
$ brew install quarkusio/tap/quarkus
$ quarkus --version
3.19.4


I use the CLI to create the new project:

Shell
 
$ quarkus create app com.example.quotes:quotes-quarkus


The CLI responds with the following output:

Shell
 
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:

Shell
 
$ 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
 
<?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:

Properties files
 
quarkus.banner.path=banner.txt
quarkus.http.port=8080
mp.openapi.scan.disable=true


Our banner.txt file has the following contents:

Plain Text
 
                   _
  __ _ _   _  ___ | |_ ___  ___
 / _` | | | |/ _ \| __/ _ \/ __|
| (_| | |_| | (_) | ||  __/\__ \
 \__, |\__,_|\___/ \__\___||___/
    |_|


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:

Java
 
@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.

Java
 
@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:

Java
 
@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:

Java
 
@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:

Java
 
@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:

Shell
 
$ quarkus dev


The command builds the project, runs the tests, and starts a local instance:

Shell
 
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:

Shell
 
curl --location 'http://localhost:8080/quotes/random'
JSON
 
{
    "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:

Quarkus developer UI

The Endpoints option provides both RESTful URLs and additional URLs to help ease the development process:

Endpoints option

We can also use the http://localhost:8080/q/swagger-ui/ URL to view the Swagger UI:

Motivational Quotes API

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:

Properties files
 
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:

Properties files
 
java.runtime.version = 17


Then, I create a Procfile in the same location for customizing the deployment behavior. This file also has one line:

Shell
 
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.

Shell
 
$ heroku login 
$ heroku create


The CLI responds with the following:

Shell
 
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:

Shell
 
$ 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:

Shell
 
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes'
JSON
 
[
    {
        "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:

Shell
 
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes/3'
JSON
 
{
    "id": 3,
    "quote": "Your time is limited, so don't waste it living someone else's life."
}


We can retrieve a random motivational quote:

Shell
 
curl --location 'https://murmuring-refuge-95709-a8582dfe9b2b.herokuapp.com/quotes/random'
JSON
 
{
    "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 that 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 had 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!

API Quarkus ChatGPT Framework

Opinions expressed by DZone contributors are their own.

Related

  • Build a REST API With Just 2 Classes in Java and Quarkus
  • Beyond Simple Responses: Building Truly Conversational LLM Chatbots
  • Multi-Tenancy and Its Improved Support in Hibernate 6.3.0
  • Model-Driven Development and Testing

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

OSZAR »