Quarkus: application upgrades using the helloworld example from JBoss EAP Quickstart

Hello everyone on this blog, and with you the fourth post from the Quarkus series! (By the way, register and go to our webinar “ This is Quarkus - Kubernetes native Java framework ”, which will be held on May 27. We will show how to start from scratch or transfer ready-made solutions) The



previous post was about how Quarkus combines MicroProfile and Spring. Recall that Quarkus is positioned as “ultrafast subatomic Java”, it is “Kubernetes-oriented Java-stack, sharpened by GraalVM and OpenJDK HotSpot and compiled from the best libraries and standards.” Today we show how to upgrade existing Java applications using Quarkus features using the helloworld application from the Red Hat JBoss Enterprise Application Platform (JBoss EAP) Quickstart repositoryusing the CDI and Servlet 3 technologies supported by Quarkus.

It is important to note here that both Quarkus and JBoss EAP focus on using tools that are built to the maximum standards. Don't have an app running on JBoss EAP? Not a problem, it can be easily migrated from the current application server to JBoss EAP using the Red Hat Application Migration Toolkit . After that, the final and working version of the upgraded code will be available in the github.com/mrizzi/jboss-eap-quickstarts/tree/quarkus repository , in the helloworld module .

This post was written using Quarkus tutorials , mainly Creating Your First Application and Building a Native Executable .

We get a code


First, create a local clone of the JBoss EAP quickstarts repository :

$ git clone https://github.com/jboss-developer/jboss-eap-quickstarts.git
Cloning into 'jboss-eap-quickstarts'...
remote: Enumerating objects: 148133, done.
remote: Total 148133 (delta 0), reused 0 (delta 0), pack-reused 148133
Receiving objects: 100% (148133/148133), 59.90 MiB | 7.62 MiB/s, done.
Resolving deltas: 100% (66476/66476), done.
$ cd jboss-eap-quickstarts/helloworld/

See how the original helloworld works


Actually, the essence of this application is clear from the name, but we will upgrade its code strictly scientific. Therefore, to begin with, look at this application in its original form.

Expand helloworld

1. Open the terminal and go to the root of the JBoss EAP folder (you can download it here ), that is, into the EAP_HOME folder.

2. Start the JBoss EAP server with the default profile:

$ EAP_HOME/bin/standalone.sh

Note: on Windows, the EAP_HOME \ bin \ standalone.bat script is used to run.

After a couple of seconds, the following should appear in the log:

[org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.2.0.GA (WildFly Core 6.0.11.Final-redhat-00001) started in 3315ms - Started 306 of 527 services (321 services are lazy, passive or on-demand)

3. Open the browser 127.0.0.1 : 8080 and see this:



Fig. 1. JBoss EAP Homepage.

4. Follow the instructions in the Build and Deploy the Quickstart manual : deploy helloworld and execute (from the project root folder) the following command:

$ mvn clean install wildfly:deploy

After successful execution of this command in the log, we will see something like the following:

[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 8.224 s

So, the first deployment of the helloworld application on JBoss EAP took just over 8 seconds.

We test helloworld

Acting strictly according to the Access the Application guide , open 127.0.0.1 : 8080 / helloworld in a browser and see this:



Fig. 2. Original Hello World from JBoss EAP.

Make changes

Change the input parameter createHelloMessage (String name) from World to Marco:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

Again, run the following command:

$ mvn clean install wildfly:deploy

Then we refresh the page in the browser and see that the text has changed:



Fig. 3. Hello Marco at JBoss EAP.

We roll back the helloworld deployment and exit JBoss EAP.

This is optional, but if you want to cancel the deployment, you can do this with the following command:

$ mvn clean install wildfly:undeploy

To shut down the JBoss EAP instance, simply press Ctrl + C in the terminal window.

Upgrading helloworld


Now let's upgrade the original helloworld application.

Create a new branch

Create a new work branch after the quickstart project finishes:

$ git checkout -b quarkus 7.2.0.GA

Changing the pom.xml file

We will start changing the application from the pom.xml file. So that Quarkus can insert XML blocks into it, execute the following command in the helloworld folder:

$ mvn io.quarkus:quarkus-maven-plugin:0.23.2:create

When writing this article, version 0.23.2 was used. Quarkus often has new versions; you can find out which version is the latest at github.com/quarkusio/quarkus/releases/latest .

The above command will insert the following elements into pom.xml:

  • The <quarkus.version> property that specifies the version of Quarkus to use.
  • The <dependencyManagement> block for importing Quarkus BOM (bill of materials) so as not to add a version for each Quarkus dependency.
  • The quarkus-maven-plugin plugin, which is responsible for packaging the application and providing development mode.
  • A native profile for creating application executables.

In addition, we manually make the following changes to pom.xml:

  1. <groupId> <parent> <artifactId>. <parent>, <groupId>.
  2. <parent>, Quarkus pom JBoss.
  3. <version> <artifactId>. .
  4. <packaging>, WAR, JAR.
  5. :
    1. javax.enterprise:cdi-api io.quarkus:quarkus-arc, <scope>provided</scope>, ( ) Quarkus- injection CDI.
    2. We change the org.jboss.spec.javax.servlet: jboss-servlet-api_4.0_spec dependency to io.quarkus: quarkus-undertow, removing the <scope> provided </scope>, because (according to the docks) this Quarkus extension provides servlet support 's.
    3. We remove the org.jboss.spec.javax.annotation: jboss-annotations-api_1.3_spec dependency, since they come bundled with the dependencies we just changed.


The version of the pom.xml file with all the changes lies at github.com/mrizzi/jboss-eap-quickstarts/blob/quarkus/helloworld/pom.xml .

Please note that the above mvn io.quarkus: quarkus-maven-plugin: 0.23.2: create command not only changes the pom.xml file, but also adds a number of components to the project, namely the following files and folders:

  • mvnw and mvnw.cmd .mvn: Maven Wrapper Maven Maven .
  • docker ( src/main/): Dockerfile native jvm ( .dockerignore).
  • resources ( src/main/): application.properties Quarkus index.html ( . Run the modernized helloworld ).

We start helloworld
To test the application, we use quarkus: dev, which starts Quarkus in development mode (for more details see this section in the Development Mode manual ).

Note: this step is expected to result in an error, because we have not yet made all the necessary changes.

Now run the command to check how it works:

$ ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< org.jboss.eap.quickstarts:helloworld >----------------
[INFO] Building Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Listening for transport dt_socket at address: 5005
INFO  [io.qua.dep.QuarkusAugmentor] Beginning quarkus augmentation
INFO  [org.jbo.threads] JBoss Threads version 3.0.0.Final
ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.jboss.as.quickstarts.helloworld.HelloService and qualifiers [@Default]
	- java member: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- declared on CLASS bean [types=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifiers=[@Default, @Any], target=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:841)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:214)
	at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:106)
	at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:249)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:780)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:415)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
	at java.lang.Thread.run(Thread.java:748)
	at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.jboss.as.quickstarts.helloworld.HelloService and qualifiers [@Default]
	- java member: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- declared on CLASS bean [types=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifiers=[@Default, @Any], target=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:428)
	at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:371)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:206)
	... 14 more

So, it doesn’t work ... And why?

The UnsatisfiedResolutionException exception points to the HelloService class, which is a member of the HelloWorldServlet class (java member: org.jboss.as.quickstarts.helloworld.HelloWorldServlet # helloService). The problem is that HelloWorldServlet needs an injected instance of HelloService, but it cannot be found (although both of these classes are in the same package).

It's time to go back to the documentation and read how Quarkus worksInjectand therefore Contexts and Dependency Injection (CDI). Therefore, we open the Contexts and Dependency Injection guide and read in the Bean Discovery section : “A bean class that does not have an annotation defining bean is not searched.”

We look at the HelloService class - it really does not have such an annotation. Therefore, it must be added so that Quarkus can search and find a bean. And since this is a stateless object, we can well add the @ApplicationScoped annotation as follows:

@ApplicationScoped
public class HelloService {

Note: here the development environment may ask you to add the required package (see the line below), and this will have to be done manually, like this:

import javax.enterprise.context.ApplicationScoped;

If in doubt about which scope to use when the source bean is not set at all, check out the JSR 365 documentation : Contexts and Dependency Injection for Java 2.0 — Default scope .

Now again, we try to start the application with the command ./mvnw compile quarkus: dev:

$ ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< org.jboss.eap.quickstarts:helloworld >----------------
[INFO] Building Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/target/classes
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Listening for transport dt_socket at address: 5005
INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 576ms
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 1.083s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

Now everything goes without errors.

We launch the upgraded helloworld
As it is written in the log, open in the browser 0.0.0.0 : 8080 (the default Quarkus start page) and see this:



Fig. 4. Start page of Quarkus dev.

The WebServlet annotation for this application has the following context definition:

@WebServlet("/HelloWorld")
public class HelloWorldServlet extends HttpServlet {

Therefore, we go to 0.0.0.0 : 8080 / HelloWorld in the browser and see the following:



Fig. 5: The Quarkus dev page for the Hello World application.

Well, everything works.

And now we are making changes to the code. Note that the ./mvnw compile quarkus: dev command is still working and we are not going to stop it. Now let's try to apply the same - trivial - changes to the code itself and see how Quarkus makes life easier for the developer:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

Save the file and then refresh the web page to see Hello Marco, as shown in the screenshot below:



Fig. 6. Hello Marco page in Quarkus dev.

Now check the output in the terminal:

INFO  [io.qua.dev] (vert.x-worker-thread-3) Changed source files detected, recompiling [/home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/src/main/java/org/jboss/as/quickstarts/helloworld/HelloWorldServlet.java]
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus stopped in 0.003s
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Beginning quarkus augmentation
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Quarkus augmentation completed in 232ms
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus 0.23.2 started in 0.257s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (vert.x-worker-thread-3) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (vert.x-worker-thread-3) Installed features: [cdi]
INFO  [io.qua.dev] (vert.x-worker-thread-3) Hot replace total time: 0.371s

The page refresh triggered the detection of changes in the source code, and Quarkus automatically performed the “stop start” procedure. And all this was completed in just 0.371 seconds (here it is, that very “ultrafast subatomic Java”).

We build helloworld into a JAR package
Now that the code works as it should, we pack it with the following command:

$ ./mvnw clean package

This command creates two JAR files in the / target folder: the helloworld-.jar file, which is a standard artifact assembled by the Maven team together with the project classes and resources. And the helloworld file is runner.jar, which is an executable JAR.

Note that this is not an uber-jar, since all dependencies are simply copied to the / target / lib folder (and not packaged into a JAR file). Therefore, to run this JAR from another folder or on a different host, you need to copy both the JAR file and the / lib folder there, given that the Class-Path element in the MANIFEST.MF file in the JAR package contains an explicit listing of the JARs from lib folders.
To learn how to create uber-jar applications, refer to the Uber-Jar Creation tutorial .

Launch helloworld, packaged in a JAR

Now you can run our JAR using the standard java command:

$ java -jar ./target/helloworld-<version>-runner.jar
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 0.673s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile prod activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

After all this is done, go to the browser on 0.0.0.0 : 8080 and check that everything works as it should.

Putting helloworld into a native executable

So, our helloworld works as a standalone Java application using Quarkus dependencies. But you can go further and turn it into a native executable file.

Install GraalVM
First of all, for this you need to install the necessary tools:

1. Download GraalVM 19.2.0.1 from github.com/oracle/graal/releases/tag/vm-19.2.0.1 .

2. Expand the downloaded archive:

$ tar xvzf graalvm-ce-linux-amd64-19.2.0.1.tar.gz

3. Go to the untar folder.

4. Run the command below to download and add a native image:

$ ./bin/gu install native-image

5. We register the folder created in step 2 into the environment variable GRAALVM_HOME:

$ export GRAALVM_HOME={untar-folder}/graalvm-ce-19.2.0.1)

For more information and installation instructions on other operating systems, see Building a Native Executable — Prerequisites .

We build helloworld into a native executable.
We read the Building a Native Executable — Producing a native executable manual : “Now create a native executable for our application to reduce its launch time and disk size. The executable file will have everything necessary to run the application, including the JVM machine (or rather, its truncated version, containing only what is needed to run the application) and our application itself. ”

To create a native executable, you must enable the Maven native profile:

$ ./mvnw package -Pnative

Our assembly took one minute and 10 seconds, and the final helloworld file - runner f was created in the / target folder.

We start the native executable file helloworld

In the previous step we got the executable file / target / helloworld - runner. Now run it:

$ ./target/helloworld-<version>-runner
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 0.006s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile prod activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

Again, open the browser 0.0.0.0 : 8080 and check that everything works as it should.

To be continued!

We believe that the method of modernizing Java applications using the capabilities of Quarkus considered in this post (albeit with the simplest example) should be actively applied in real life. In this case, you are likely to encounter a number of problems, the solution of which we will partially consider in the next post, which will focus on how to measure memory consumption in order to evaluate performance improvements, an important part of the entire application upgrade process.

All Articles