How to create Quarkus Command Mode applications

Quarkus is set of technologies to develop an entire Microservice architecture. The foundation of this architecture is typically an HTTP server, serving REST Endpoints. It is however also possible to create powerful Java scripts using Quarkus advanced sets of APIs. In this tutorial we will learn how to create standalone Quarkus applications with a bare simple main entry point.

Quarkus as scripting tool

As Quarkus ecosystem grows, there’s an increase in the number of options to create Quarkus runnable scripts.

In this tutorial we have discussed how to create a powerful scripts with JBang and Quarkus: JBang: Create Java scripts like a pro

On the other hand, if you need to create a more complex standalone application, which includes some layers of complexity, you can use Quarkus Command Mode. When using Quarkus Command Mode, you typically would replace the resteasy dependency with the tiny quarkus-arc which provides the basic Dependency Injection mechanism to your application:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-arc</artifactId>
</dependency>

Let’s start with an example application which simply inserts a row in a Database, using the Command Line arguments.

Create a Quarkus Command Mode application

Firstly, create your project:

mvn io.quarkus:quarkus-maven-plugin:2.3.0.Final:create \
     -DprojectGroupId=com.mastertheboss.quarkus \
     -DprojectArtifactId=command-line-demo \
     -DclassName="com.mastertheboss.quarkus.service.TicketService" \
     -Dextensions="quarkus-arc,quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql"

Next, let’s add a Class to our project:

import javax.inject.Inject;

import com.mastertheboss.quarkus.model.Ticket;
import com.mastertheboss.quarkus.service.TicketService;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class TicketMain implements QuarkusApplication {

	@Inject
	TicketService service;

	@Override
	public int run(String... args) {

		if(args.length<2) {
			System.out.println("Usage: mvn quarkus:dev  -Dquarkus.args=\"<name> <seat>\"");
			return 1;
		}

		Ticket ticket = new Ticket();
		ticket.name=args[0];
		ticket.seat=args[1];
		service.createTicket(ticket);
		return 0;
	}


}

The @QuarkusMain annotation tells Quarkus that this is the main entry point.
The run method is invoked once Quarkus starts, and the application stops when it finishes.

Behind the hoods, the @QuarkusMain instance is an application scoped bean by default. It has access to singletons, application and dependent scoped beans.

If you prefer, there’s another option to create a QuarkusMain application: you can include the standard Java static main method, and use the Java main method to launch Quarkus. Simple example:

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class JavaMain {

    public static void main(String... args) {
        Quarkus.run(TicketMain.class, args);
    }
}

Next, let’s code the TicketService which persists the Ticket entry in the database:

import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;

import com.mastertheboss.quarkus.model.Ticket;

@ApplicationScoped
public class TicketService {
    @Transactional
    public void createTicket(Ticket ticket) {
        ticket.persist();
        System.out.println("Ticket created");
    }

}

Finally, the Entity Class which extends PanacheEntity:

import javax.persistence.Column;
import javax.persistence.Entity;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Ticket extends PanacheEntity {

    @Column(length = 20, unique = true)
    public String name;

    @Column(length = 3, unique = true)
    public String seat;

    public Ticket() {
    }

    public Ticket(String name, String seat) {
        this.name = name;
        this.seat = seat;
    }
}

To learn more about Panache: Data Persistence with Quarkus and Hibernate Panache

To connect to PostgreSQL we will add the following configuration in application.properties:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkusdb

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Running the Command Mode application

Our application is ready to be run. Before that, we need to start a PostgreSQL server:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=quarkusdb -p 5432:5432 postgres

Finally, we can run the application. We will pass arguments to the Main Class you can use the -Dquarkus.args option:

mvn quarkus:dev -Dquarkus.args="John AB1"

The application will start and, as you can see from the logs, it inserts the Ticket in the database:

Coding a Test Main class

Lastly, we will show how to create a JUnit test for Quarkus Main applications. The simplest way to do that is decorating the test class with @QuarkusMainTest. In addition, we will add multiple @Launch points, each one with its own set of parameters and exit code:

import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import io.quarkus.test.junit.main.QuarkusMainTest;

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

@QuarkusMainTest
public class TicketServiceTest {

    @Test
    @Launch({ "John", "AB1"})
    public void testLaunchCommand(LaunchResult result) {
    	assertTrue(result.getOutput().indexOf("Ticket created") > 0 );
    }

    @Test
    @Launch(value = {}, exitCode = 1)
    public void testLaunchCommandFailed() {
    }

   
}

As a result, both Tests will run and complete successfully:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mastertheboss.quarkus.service.TicketServiceTest
. . . . .
2021-10-18 11:34:36,754 INFO  [io.quarkus] (main) Quarkus stopped in 0.012s
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.774 s - in com.mastertheboss.quarkus.service.TicketServiceTest

Source code available here:

https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/command-line-demo