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

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

SBOMs are essential to circumventing software supply chain attacks, and they provide visibility into various software components.

Related

  • Keep Your Application Secrets Secret
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java
  • High-Performance Reactive REST API and Reactive DB Connection Using Java Spring Boot WebFlux R2DBC Example
  • Spring Boot GoT: Game of Trace!

Trending

  • When MySQL, PostgreSQL, and Oracle Argue: Doris JDBC Catalog Acts as the Peacemaker
  • Streamline Your ELT Workflow in Snowflake With Dynamic Tables and Medallion Design
  • Decoding Database Speed: Essential Server Resources and Their Impact
  • Contract-Driven ML: The Missing Link to Trustworthy Machine Learning
  1. DZone
  2. Coding
  3. Frameworks
  4. How to Introduce a New API Quickly Using Micronaut

How to Introduce a New API Quickly Using Micronaut

Knowing when to pivot can be vital to staying ahead of the competition. See how Cursor AI and Heroku can be leveraged to transform an idea into a reality.

By 
John Vester user avatar
John Vester
DZone Core CORE ·
May. 28, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
13.1K Views

Join the DZone community and get the full member experience.

Join For Free

In the first two articles of this series (part 1 and part 2), I demonstrated how quickly an idea can become a reality using Spring Boot, the framework I have used for over 10 years to establish new services. I stepped out of my comfort zone in the last article (part 3) when I used Quarkus for the first time, which offered a really nice CLI to assist with the development process.

I would like to close out this short series with another framework that’s new (to me), called Micronaut.

Micronaut is an open-source JDK-based framework focused on lightweight microservices. Under the hood, Micronaut does not rely on reflective programming, but emphasizes an inversion of control (IoC) design, which results in less memory usage and a much faster start time. A robust CLI exists, too.

While the service should start fast, the biggest question I have is: “How quickly can I transform my motivation quotes idea to a reality using Micronaut?”

Getting Started With Micronaut

As noted above, just like with Spring Boot and Quarkus, Micronaut also has a CLI (mn). There is also an initializer (called a launcher), which is very similar to what Spring Boot offers. For this article, we’ll use the CLI, and I’ll use Homebrew to perform the install:

Shell
 
$ brew install --cask micronaut-projects/tap/micronaut


Other installation options can be found here.

To get started, we’ll issue the mn command:

Shell
 
$ mn


For macOS users, you will likely need to visit the Privacy & Security section in System Settings to allow the use of the mn command.

We’ll simply use create to interactively initialize a new service:

Shell
 
mn> create

Apr 01, 2025 2:52:20 PM org.jline.utils.Log logr
WARNING: Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
What type of application do you want to create? (enter for default)
*1) Micronaut Application
 2) Micronaut CLI Application
 3) Micronaut Serverless Function
 4) Micronaut gRPC Application
 5) Micronaut Messaging Application


We’ll select the default option to create a new application.

Shell
 
Choose your preferred language. (enter for default)
*1) Java
 2) Groovy
 3) Kotlin


We’ll use Java for this exercise.

Shell
 
Choose your preferred test framework. (enter for default)
*1) JUnit
 2) Spock
 3) Kotest


We’ll use JUnit for our test framework.

Shell
 
Choose your preferred build tool. (enter for default)
*1) Gradle (Groovy)
 2) Gradle (Kotlin)
 3) Maven


We’ll plan to use Gradle (Groovy) this time.

Shell
 
Choose the target JDK. (enter for default)
*1) 17
 2) 21


We’ll stick with Java 17, since it matches what we’ve used for other articles in this series.

Shell
 
Enter any features to apply. Use tab for autocomplete and separate by a space.


For now, we’ll add support for OpenAPI and Swagger and press the return key:

Shell
 
openapi swagger-ui
Shell
 
Enter a name for the project.


We’ll use the name quotes-micronaut for our project.

Now let’s exit out of mn using Control-C and check out the base directory structure:

Shell
 
$ cd quotes-micronaut && ls -la
total 88
drwxr-xr-x@ 13 johnvester   416 Apr  1 14:58 .
drwxrwxrwx  93 root        2976 Apr  1 14:58 ..
-rw-r--r--@  1 johnvester   127 Apr  1 14:58 .gitignore
-rw-r--r--@  1 johnvester  1380 Apr  1 14:58 README.md
-rw-r--r--@  1 johnvester  1590 Apr  1 14:58 build.gradle
drwxr-xr-x@  3 johnvester    96 Apr  1 14:58 gradle
-rw-r--r--@  1 johnvester    23 Apr  1 14:58 gradle.properties
-rwxr--r--@  1 johnvester  8762 Apr  1 14:58 gradlew
-rw-r--r--@  1 johnvester  2966 Apr  1 14:58 gradlew.bat
-rw-r--r--@  1 johnvester   367 Apr  1 14:58 micronaut-cli.yml
-rw-r--r--@  1 johnvester   182 Apr  1 14:58 openapi.properties
-rw-r--r--@  1 johnvester    39 Apr  1 14:58 settings.gradle
drwxr-xr-x@  4 johnvester   128 Apr  1 14:58 src


We can open the project in IntelliJ and run the Application class:

Shell
 
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
15:01:56.083 [main] INFO  io.micronaut.runtime.Micronaut - 
Startup completed in 648ms. Server Running: http://localhost:8080


We can validate that the RESTful API is working, too, by calling the URI configured in the QuotesMicronautController class.

Java
 
@Controller("/quotes-micronaut")
public class QuotesMicronautController {
    @Get(uri="/", produces="text/plain")
    public String index() {
        return "Example Response";
    }
} 


We’ll use the following cURL command:

Shell
 
curl --location 'http://localhost:8080/quotes-micronaut'


This returns the following text:

Shell
 
Example Response


The only difference I saw with using the CLI over the launcher is that I wasn’t able to specify a base package. That’s okay, because we can use the CLI to establish the rest of our classes:

Shell
 
$ mn create-bean quotes.micronaut.repositories.QuotesRepository
$ mn create-bean quotes.micronaut.services.QuotesService
$ mn create-bean quotes.micronaut.controllers.QuotesController


You’ll notice we are creating our own controller class. This is simply to follow the same approach I’ve used in my earlier articles. The auto-generated QuotesMicronautController would have worked just fine.

To make things less complicated for the injection side of things, we can use Lombok, adding the following dependencies to the build.gradle file:

Groovy
 
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")


Minor Roadblock With OpenAPI Generation

When I looked up the server-side OpenAPI generators, I was excited to see java-micronaut-server as an option. Upon further review, the documentation indicated this has a stability level of “BETA.” 

The beta version was able to generate the expected Quote model object, but I ran into issues trying to generate an interface or abstract class that my controllers could extend. So I decided to create an issue in the openapi-generator library (link for those who are interested in the details) and pivot toward another approach.

Using Cursor AI to Convert My Quarkus Service

While sharing this experience with Alvin Lee (a longtime colleague), he had an idea. Alvin asked, “What if we use Cursor AI to analyze your Quarkus repo and port it to Micronaut for us?”

I told Alvin to “go for it” and became excited, because this article really had not required the use of AI like the other articles in the series.

I gave Alvin access to fork my Quarkus repo and within a matter of minutes, the service was completely ported to Micronaut. Let’s quickly review the classes that were created automatically.

First, let’s review the repository layer:

Java
 
@Singleton
public class QuotesRepository {
  private static final List<Quote> QUOTES = List.of(
    Quote.builder()
      .id(1)
      .quote("The greatest glory in living lies not in never falling, but in rising every time we fall.")
      .build(),
    Quote.builder()
      .id(2)
      .quote("The way to get started is to quit talking and begin doing.")
      .build(),
    Quote.builder()
      .id(3)
      .quote("Your time is limited, so don't waste it living someone else's life.")
      .build(),
    Quote.builder()
      .id(4)
      .quote("If life were predictable it would cease to be life, and be without flavor.")
      .build(),
    Quote.builder()
      .id(5)
      .quote("If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success.")
      .build()
  );

  public List<Quote> getAllQuotes() {
    return QUOTES;
  }

  public Optional<Quote> getQuoteById(Integer id) {
    return QUOTES.stream().filter(quote -> 
      quote.getId().equals(id)).findFirst();
  }
}


Next, let’s take a look at the service layer:

Java
 
@Singleton
public class QuotesService {
  private final QuotesRepository quotesRepository;

  @Inject
  public QuotesService(QuotesRepository quotesRepository) {
    this.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, we can examine the controller that will be the consumer-facing interface to our API:

Java
 
@Controller("/quotes")
public class QuotesController {
  @Inject
  private QuotesService quotesService;

  @Get
  public List<Quote> getAllQuotes() {
    return quotesService.getAllQuotes();
  }

  @Get("/{id}")
  public Optional<Quote> getQuoteById(@PathVariable Integer id) {
    return quotesService.getQuoteById(id);
  }

  @Get("/random")
  public Quote getRandomQuote() {
    return quotesService.getRandomQuote();
  }
}


Validating Our Service Locally

To validate the service locally, we’ll start the service from IntelliJ like we did before:

Shell
 
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
21:18:54.740 [main] INFO  io.micronaut.runtime.Micronaut - 
Startup completed in 284ms. Server Running: http://localhost:8080


With all the code in place, the Micronaut service started in just 284 milliseconds.

We’ll use the following cURL command to retrieve a random motivational quote:

Shell
 
curl --location 'http://localhost:8080/quotes/random'


I received a 200 OK HTTP response and the following JSON payload:

JSON
 
{
  "id": 3,
  "quote": "Your time is limited, so don't waste it living someone else's life."
}


Looks like the port was quick and successful! Now it is time to deploy.

Leveraging Heroku to Deploy the Service

Since I used Heroku for my prior articles, I wondered if support existed for Micronaut services. 

Although Heroku recently published some updated information regarding Micronaut support in its Java buildpack, I felt like I could use my existing experience to get things going. Selecting Heroku helps me deploy my services quickly, and I don’t lose time dealing with infrastructure concerns.

The first thing I needed to do was add the following line to the application.properties file to make sure the server’s port could be overridden by Heroku:

Properties files
 
micronaut.server.port=${PORT:8080}


Next, I created a system.properties file to specify we are using Java version 17:

Properties files
 
java.runtime.version = 17


Knowing that Heroku runs the “stage” Gradle task as part of the deployment, we can specify this in the manifest file we plan to use by adding the following lines to the build.gradle file:

Groovy
 
jar {
  manifest {
    attributes(
      'Main-Class': 'quotes.micronaut.Application'
    )
  }
}

task stage(dependsOn: ['build', 'clean'])

build.mustRunAfter clean


Ordinarily, for running apps on Heroku, we would need to add a Procfile, where we specify the Heroku environment, reference the path to where the stage-based JARs will reside, and use the port set by Heroku. That file might look like this:

Shell
 
web: java $JAVA_OPTS -Dmicronaut.environments=heroku -Dserver.port=$PORT -jar build/libs/*.jar


However, on detecting a Micronaut app, the Heroku buildpack defaults to this:

Shell
 
java -Dmicronaut.server.port=$PORT $JAVA_OPTS -jar build/libs/*.jar


… and that's exactly what I need. No need for a Procfile after all! That's pretty slick.

Now we just need to log in to Heroku and create a new application:

Shell
 
$ heroku login 
$ heroku create


The CLI responds with the following response:

Shell
 
Creating app... done, aqueous-reef-26810
https://aqueous-reef-26810-4db1994daff4.herokuapp.com/ | 
https://git.heroku.com/aqueous-reef-26810.git


The Heroku app instance is named aqueous-reef-26810-4db1994daff4, so my service will run at https://aqueous-reef-26810-4db1994daff4.herokuapp.com/.

One last thing to do… push the code to Heroku, which deploys the service:

Shell
 
$ git push heroku main


Switching to the Heroku dashboard, we see our service has deployed successfully:


We are now ready to give our service a try on the internet.

Motivational Quotes in Action

Using the Heroku app URL, https://aqueous-reef-26810-4db1994daff4.herokuapp.com/, we can now test our Motivational Quotes API using curl commands. 

First, we retrieve the list of quotes:

Shell
 
curl --location 'https://aqueous-reef-26810-4db1994daff4.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://aqueous-reef-26810-4db1994daff4.herokuapp.com/quotes/4'
JSON
 
{
    "id": 4,
    "quote": "If life were predictable it would cease to be life, and be without flavor."
}


We can retrieve a random motivational quote:

Shell
 
curl --location 'https://aqueous-reef-26810-4db1994daff4.herokuapp.com/quotes/random'
JSON
 
{
    "id": 3,
    "quote": "Your time is limited, so don't waste it living someone else's life."
}


We can even view the auto-generated Swagger UI via Heroku:

Conclusion

In this article, I had to step outside my comfort zone again — this time working with Micronaut for the very first time. Along the way, I ran into an unexpected issue which caused me to pivot my approach to leverage AI — more specifically, Cursor. Once ready, I was able to use my existing knowledge to deploy the service to Heroku and validate everything was working as expected.

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

Micronaut offered a fully functional CLI and a service that started the fastest of all the frameworks in the series. Cursor helped us stay on track by porting our Quarkus service to Micronaut. Like before, Heroku provided a fast and easy way to deploy the service without having to worry about any infrastructure concerns.

All three key solutions fully adhered to my mission statement, allowing me to not only be able to focus on my idea, but to make it an internet-accessible reality… quickly!

If you are interested in the source code for this article, it is available on GitLab.

Have a really great day!

API Command-line interface Java (programming language) shell Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Keep Your Application Secrets Secret
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java
  • High-Performance Reactive REST API and Reactive DB Connection Using Java Spring Boot WebFlux R2DBC Example
  • Spring Boot GoT: Game of Trace!

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: