Skip to content

Latest commit

 

History

History
456 lines (329 loc) · 12 KB

demo.adoc

File metadata and controls

456 lines (329 loc) · 12 KB

Native Java REST API Demo Steps

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:

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.

Install a JDK with GraalVM

Use SDKMAN to install Java 21 with GraalVM

sdk install java 21.0.2-graalce

Generate an OAuth 2.0 Access Token

  1. Install the Auth0 CLI and run auth0 login to connect it to your account.

  2. Create an access token using Auth0’s CLI:

    auth0 test token -a https://<your-auth0-domain>/api/v2/ -s openid
  3. Set the access token as a TOKEN environment variable in a terminal window.

    TOKEN=eyJraWQiOiJYa2pXdjMzTDRBYU1ZSzNGM...

Make a Java REST API with Micronaut

  1. 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
  2. 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() + "!";
        }
    
    }
  3. 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

Run and Test Your Micronaut API with HTTPie

  1. Start your app:

    ./gradlew run
  2. 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.

Build a Native Micronaut App

  1. Compile your Micronaut app into a native binary:

    ./gradlew nativeCompile
  2. Start your Micronaut app:

    ./build/native/nativeCompile/app
  3. 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"

Create a Java REST API with Quarkus

  1. 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
  2. Rename GreetingResource.java to HelloResource.java and add user information to the hello() 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() + "!";
        }
    }
  3. 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
  4. Rename GreetingResourceTest to HelloResourceTest 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 and Test Your Quarkus API with HTTPie

  1. Run your Quarkus app:

    quarkus dev
    ./gradlew --console=plain quarkusDev
  2. Test it from another terminal:

    http :8080/hello
  3. Test with access token:

    http :8080/hello Authorization:"Bearer $TOKEN"

Build a Native Quarkus App

  1. Compile your Quarkus app into a native binary:

    quarkus build --native
    ./gradlew build -Dquarkus.package.type=native
  2. Start your Quarkus app:

    ./build/quarkus-1.0.0-SNAPSHOT-runner
  3. Test it with HTTPie and an access token:

    http :8080/hello Authorization:"Bearer $TOKEN"

Start a Java REST API with Spring Boot

  1. 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
    Caution
    Spring Boot 3.2.1 and 3.2.2 do not work with GraalVM.
  2. 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() + "!";
        }
    
    }
  3. 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>/

Run and Test Your Spring Boot API with HTTPie

  1. Start your app from your IDE or using a terminal:

    ./gradlew bootRun
  2. Test your API with an access token.

    http :8080/hello Authorization:"Bearer $TOKEN"

Build a Native Spring Boot App

  1. Compile your Spring Boot app into a native executable:

    ./gradlew nativeCompile
    Tip
    To build a native app and a Docker container, use the Spring Boot Gradle plugin and ./gradlew bootBuildImage.
  2. Start your Spring Boot app:

    ./build/native/nativeCompile/spring-boot
  3. Test your API with an access token.

    http :8080/hello Authorization:"Bearer $TOKEN"

Build a Java REST API with Helidon

  1. 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
    Tip
    See Migrating a Helidon SE application to Gradle for Gradle support.
  2. Add MicroProfile JWT support in pom.xml:

    <dependency>
        <groupId>io.helidon.microprofile.jwt</groupId>
        <artifactId>helidon-microprofile-jwt-auth</artifactId>
    </dependency>
  3. 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() + "!";
        }
    }
  4. Add a HelloApplication class in src/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);
        }
    }
  5. 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

Run and Test Your Helidon REST API with HTTPie

  1. Start your app from your IDE or using a terminal:

    helidon dev
  2. Test your API with an access token.

    http :8080/hello Authorization:"Bearer $TOKEN"
  3. Delete the default Java classes created by the Helidon CLI:

    • On Windows: del /s *.java

    • On Mac/Linux: find . -name '*.java' -delete

Build a native Helidon app with GraalVM

  1. Compile your Helidon app into a native executable using the native-image profile:

    mvn package -Pnative-image
  2. Start your Helidon app:

    ./target/helidon
  3. Test your API with an access token.

    http :8080/hello Authorization:"Bearer $TOKEN"

Startup Time Comparison

  1. Run each image three times before recording the numbers, then each command five times.

    Tip
    Use the start.sh script to get the real time, not what each framework prints to the console.
  2. 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
Table 1. Native Java startup times in milliseconds
Framework Command executed Milliseconds to start

Micronaut

./micronaut/build/native/nativeCompile/app

38.4

Micronaut (optimized)

./micronaut/build/native/nativeOptimizedCompile/app

39

Quarkus

./quarkus/build/quarkus-1.0.0-SNAPSHOT-runner

42

Spring Boot

./spring-boot/build/native/nativeCompile/spring-boot

60.4

Helidon

./helidon/target/helidon

54.8

Helidon (optimized)

auth0-java-rest-api-examples/pull/2

50.2

Memory Usage Comparison

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:

Table 2. Native Java memory used in megabytes
Framework Executable Megabytes after startup Megabytes after 1 request Megabytes after 10 requests

Micronaut

app

53

62

66

Micronaut (optimized)

app

53

63

67

Quarkus

quarkus

37

48

52

Spring Boot

spring-boot

76

86

87

Helidon

helidon

82

92

94

Helidon (optimized)

helidon

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

Native Java REST APIs FTW!

🚀 Find the code on GitHub: @oktadev/auth0-java-rest-api-examples