In this demo, I’ll show how to create native images with Micronaut, Quarkus, Spring Boot, and Helidon. You’ll see how to run a secure, OAuth 2.0-protected, Java REST API that allows JWT authentication.
Prerequisites:
-
SDKMAN (for Java 21 with GraalVM)
-
HTTPie (a better version of cURL)
-
A free Auth0 account and the Auth0 CLI
Tip
|
The brackets at the end of some steps indicate the IntelliJ Live Templates to use. You can find the template definitions at mraible/idea-live-templates. |
Use SDKMAN to install Java 21 with GraalVM
sdk install java 21.0.2-graalce
-
Install the Auth0 CLI and run
auth0 login
to connect it to your account. -
Create an access token using Auth0’s CLI:
auth0 test token -a https://<your-auth0-domain>/api/v2/ -s openid
-
Set the access token as a
TOKEN
environment variable in a terminal window.TOKEN=eyJraWQiOiJYa2pXdjMzTDRBYU1ZSzNGM...
-
Use SDKMAN to install Micronaut’s CLI and create an app:
sdk install micronaut mn create-app com.okta.rest.app -f security-jwt -f micronaut-aot mv app micronaut
-
Create
controller/HelloController.java
: [mn-hello
]package com.okta.rest.controller; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Produces; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; import java.security.Principal; @Controller("/hello") public class HelloController { @Get @Secured(SecurityRule.IS_AUTHENTICATED) @Produces(MediaType.TEXT_PLAIN) public String hello(Principal principal) { return "Hello, " + principal.getName() + "!"; } }
-
Enable and configure JWT security in
src/main/resources/application.properties
: [mn-security-config
]micronaut.security.token.jwt.signatures.jwks.okta.url=https://dev-06bzs1cu.us.auth0.com/.well-known/jwks.json
-
Start your app:
./gradlew run
-
Use HTTPie to pass the JWT in as a bearer token in the
Authorization
header:http :8080/hello Authorization:"Bearer $TOKEN"
You should get a 200 response with your user id in it.
-
Compile your Micronaut app into a native binary:
./gradlew nativeCompile
-
Start your Micronaut app:
./build/native/nativeCompile/app
-
Test it with HTTPie and an access token. You may have to generate a new JWT if yours has expired.
http :8080/hello Authorization:"Bearer $TOKEN"
-
Use SDKMAN to install the Quarkus CLI and create a new app with JWT support:
sdk install quarkus quarkus create app com.okta.rest:quarkus \ --extension="smallrye-jwt,resteasy-reactive" \ --gradle
-
Rename
GreetingResource.java
toHelloResource.java
and add user information to thehello()
method: [qk-hello
]package com.okta.rest; import io.quarkus.security.Authenticated; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.SecurityContext; import java.security.Principal; @Path("/hello") public class HelloResource { @GET @Authenticated @Produces(MediaType.TEXT_PLAIN) public String hello(@Context SecurityContext context) { Principal userPrincipal = context.getUserPrincipal(); return "Hello, " + userPrincipal.getName() + "!"; } }
-
Add your Auth0 endpoints to
src/main/resources/application.properties
: [qk-properties
]mp.jwt.verify.issuer=https://<your-auth0-domain>/ mp.jwt.verify.publickey.location=${mp.jwt.verify.issuer}.well-known/jwks.json
-
Rename
GreetingResourceTest
toHelloResourceTest
and modify it to expect a 401 instead of a 200:package com.okta.rest; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; @QuarkusTest public class HelloResourceTest { @Test public void testHelloEndpoint() { given() .when().get("/hello") .then() .statusCode(401); } }
-
Run your Quarkus app:
quarkus dev ./gradlew --console=plain quarkusDev
-
Test it from another terminal:
http :8080/hello
-
Test with access token:
http :8080/hello Authorization:"Bearer $TOKEN"
-
Use SDKMAN to install the Spring Boot CLI. Then, create a Spring Boot app with OAuth 2.0 support:
sdk install springboot spring init -d=web,oauth2-resource-server,native -b=3.2.0 \ --group-id=com.okta.rest --package-name=com.okta.rest spring-boot
CautionSpring Boot 3.2.1 and 3.2.2 do not work with GraalVM. -
Add a
HelloController
class that returns the user’s information: [sb-hello
]package com.okta.rest.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.security.Principal; @RestController public class HelloController { @GetMapping("/hello") public String hello(Principal principal) { return "Hello, " + principal.getName() + "!"; } }
-
Configure the app to be an OAuth 2.0 resource server by adding the issuer to
application.properties
.spring.security.oauth2.resourceserver.jwt.issuer-uri=https://<your-auth0-domain>/
-
Start your app from your IDE or using a terminal:
./gradlew bootRun
-
Test your API with an access token.
http :8080/hello Authorization:"Bearer $TOKEN"
-
Compile your Spring Boot app into a native executable:
./gradlew nativeCompile
TipTo build a native app and a Docker container, use the Spring Boot Gradle plugin and ./gradlew bootBuildImage
. -
Start your Spring Boot app:
./build/native/nativeCompile/spring-boot
-
Test your API with an access token.
http :8080/hello Authorization:"Bearer $TOKEN"
-
Use SDKMAN to install the Helidon CLI. Then, create a Helidon app:
sdk install helidon helidon init --flavor MP --groupid com.okta.rest \ --artifactid helidon --package com.okta.rest --batch
TipSee Migrating a Helidon SE application to Gradle for Gradle support. -
Add MicroProfile JWT support in
pom.xml
:<dependency> <groupId>io.helidon.microprofile.jwt</groupId> <artifactId>helidon-microprofile-jwt-auth</artifactId> </dependency>
-
Add a
HelloResource
class that returns the user’s information: [h-hello
]package com.okta.rest.controller; import io.helidon.security.Principal; import io.helidon.security.annotations.Authenticated; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Context; @Path("/hello") public class HelloResource { @Authenticated @GET public String hello(@Context SecurityContext context) { return "Hello, " + context.userName() + "!"; } }
-
Add a
HelloApplication
class insrc/main/java/com/okta/rest
to register your resource and configure JWT authentication: [h-app
]package com.okta.rest; import com.okta.rest.controller.HelloResource; import org.eclipse.microprofile.auth.LoginConfig; import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.core.Application; import java.util.Set; @LoginConfig(authMethod = "MP-JWT") @ApplicationScoped public class HelloApplication extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(HelloResource.class); } }
-
Add your Auth0 endpoints to
src/main/resources/META-INF/microprofile-config.properties
.mp.jwt.verify.issuer=https://<your-auth0-domain>/ mp.jwt.verify.publickey.location=${mp.jwt.verify.issuer}.well-known/jwks.json
-
Start your app from your IDE or using a terminal:
helidon dev
-
Test your API with an access token.
http :8080/hello Authorization:"Bearer $TOKEN"
-
Delete the default Java classes created by the Helidon CLI:
-
On Windows:
del /s *.java
-
On Mac/Linux:
find . -name '*.java' -delete
-
-
Run each image three times before recording the numbers, then each command five times.
TipUse the start.sh
script to get the real time, not what each framework prints to the console. -
Write each time down, add them up, and divide by five for the average. For example:
Micronaut: (37 + 37 + 40 + 38 + 40) / 5 = 38.4 Micronaut (optimized): (36 + 39 + 41 + 39 + 40) / 5 = 39 Quarkus: (35 + 48 + 49 + 35 + 43) / 5 = 42 Spring Boot: (57 + 57 + 62 + 63 + 63) / 5 = 60.4 Helidon: (67 + 49 + 52 + 50 + 56) / 5 = 54.8 Helidon (optimized): (52 + 50 + 48 + 50 + 51) / 5 = 50.2
Framework | Command executed | Milliseconds to start |
---|---|---|
Micronaut |
|
38.4 |
Micronaut (optimized) |
|
39 |
Quarkus |
|
42 |
Spring Boot |
|
60.4 |
Helidon |
|
54.8 |
Helidon (optimized) |
50.2 |
Test the memory usage in MB of each app using the command below. Make sure to send an HTTP request to each one before measuring.
ps -o pid,rss,command | grep --color <executable> | awk '{$2=int($2/1024)"M";}{ print;}'
Substitute <executable>
as follows:
Framework | Executable | Megabytes after startup | Megabytes after 1 request | Megabytes after 10 requests |
---|---|---|---|---|
Micronaut |
|
53 |
62 |
66 |
Micronaut (optimized) |
|
53 |
63 |
67 |
Quarkus |
|
37 |
48 |
52 |
Spring Boot |
|
76 |
86 |
87 |
Helidon |
|
82 |
92 |
94 |
Helidon (optimized) |
|
62 |
72 |
74 |
Important
|
If you disagree with these numbers and think X framework should be faster, I encourage you to clone this repo and run these tests yourself. |
./build.sh ./start.sh micronaut|quarkus|spring-boot|helidon ./memory.sh $TOKEN micronaut|quarkus|spring-boot|helidon
🚀 Find the code on GitHub: @oktadev/auth0-java-rest-api-examples