This article shows how to start a Grizzly HTTP server to run a JAX-RS or Eclipse Jersey application.

Tested with

  • Jersey 3.0.2
  • Grizzly 3 HTTP server
  • Java 8
  • Maven
  • JUnit 5

Jersey basics

  • Many Java-based HTTP servers deploy Jersey applications, for example, JDK HTTP Server, Grizzly, SimpleJetty and Netty. Read this Jersey and HTTP servers.
  • Jersey uses HK2 dependency injection framework.
  • Jersey uses Java APIs logging, or JUL, or java.util.logging.*.

Maven CLI

mvn archetype:generate "-DgroupId=com.favtuts" "-DartifactId=jersey-hello-world-example" "-Dpackage=com.favtuts" "-Dversion=1.0-SNAPSHOT" "-DarchetypeArtifactId=maven-archetype-webapp" "-DinteractiveMode=false"

In windows, you should add quotes for every parameter, if not you will encounter the error: “The goal you specified requires a project to execute but there is no POM in this directory

* https://stackoverflow.com/questions/16348459/error-the-goal-you-specified-requires-a-project-to-execute-but-there-is-no-pom

* https://stackoverflow.com/questions/6704813/maven-generating-pom-file/11199865#11199865

1. Project Directory

A standard Maven project directory.

tree /F
C:.
│   pom.xml
│
├───src
│   ├───main
│   │   ├───java
│   │   │   └───com
│   │   │       └───favtuts
│   │   │           │   MainApp.java
│   │   │           │
│   │   │           ├───config
│   │   │           │       AutoScanFeature.java
│   │   │           │
│   │   │           ├───resource
│   │   │           │       MyResource.java
│   │   │           │
│   │   │           └───service
│   │   │                   MessageService.java
│   │   │                   MessageServiceImpl.java
│   │   │
│   │   └───resources
│   └───test
│       └───java
│           └───com
│               └───favtuts
│                       MyResourceTest.java
│
└───target

2. Jersey dependencies

The main Jersey dependencies are the jersey-bom and jersey-hk2, others are optional, read the comment for self-explanatory.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
      http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.favtuts</groupId>
    <artifactId>jersey-hello-world-example</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>jersey-hello-world-example</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
        <junit.version>5.4.0</junit.version>
        <jsonassert.version>1.5.0</jsonassert.version>
        <jersey.version>3.0.2</jersey.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <!-- Grizzly2 HTTP Server -->
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>

        <!-- Jersey related and HK2 dependency injection -->
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <!-- add jackson for json conversion
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
        </dependency>
        -->

        <!-- generates the META-INF/hk2-locator/default files for auto scan and discovery -->
        <dependency>
            <groupId>org.glassfish.hk2</groupId>
            <artifactId>hk2-metadata-generator</artifactId>
            <version>3.0.2</version>
        </dependency>

        <!-- Need this to hide warning for jakarta.activation.DataSource -->
        <dependency>
            <groupId>jakarta.activation</groupId>
            <artifactId>jakarta.activation-api</artifactId>
            <version>2.0.1</version>
        </dependency>

        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <!-- JUnit 5 need at least 2.22.0 to support -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>

            <!-- create a thin-jar -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.favtuts.MainApp</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <!-- copy project dependencies -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.2</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- we need runtime dependency only -->
                            <includeScope>runtime</includeScope>
                            <outputDirectory>${project.build.directory}/lib/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

</project>

Review the complete Jersey dependencies for a simple Jersey application.

Terminal

$ mvn dependency:tree

[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< com.favtuts:jersey-hello-world-example >---------------
[INFO] Building jersey-hello-world-example 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ jersey-hello-world-example ---
[INFO] com.favtuts:jersey-hello-world-example:jar:1.0
[INFO] +- org.glassfish.jersey.containers:jersey-container-grizzly2-http:jar:3.0.2:compile
[INFO] |  +- jakarta.inject:jakarta.inject-api:jar:2.0.0:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-server:jar:3.0.0:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-http:jar:3.0.0:compile
[INFO] |  |     \- org.glassfish.grizzly:grizzly-framework:jar:3.0.0:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-common:jar:3.0.2:compile
[INFO] |  |  \- org.glassfish.hk2:osgi-resource-locator:jar:1.0.3:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-server:jar:3.0.2:compile
[INFO] |  |  +- org.glassfish.jersey.core:jersey-client:jar:3.0.2:compile
[INFO] |  |  \- jakarta.validation:jakarta.validation-api:jar:3.0.0:compile
[INFO] |  \- jakarta.ws.rs:jakarta.ws.rs-api:jar:3.0.0:compile
[INFO] +- org.glassfish.jersey.inject:jersey-hk2:jar:3.0.2:compile
[INFO] |  +- org.glassfish.hk2:hk2-locator:jar:3.0.1:compile
[INFO] |  |  \- org.glassfish.hk2.external:aopalliance-repackaged:jar:3.0.1:compile
[INFO] |  \- org.javassist:javassist:jar:3.25.0-GA:compile
[INFO] +- org.glassfish.hk2:hk2-metadata-generator:jar:3.0.2:compile
[INFO] |  +- org.glassfish.hk2:hk2-api:jar:3.0.2:compile
[INFO] |  +- org.glassfish.hk2:hk2-utils:jar:3.0.2:compile
[INFO] |  \- jakarta.annotation:jakarta.annotation-api:jar:2.0.0:compile
[INFO] +- jakarta.activation:jakarta.activation-api:jar:2.0.1:compile
[INFO] \- org.junit.jupiter:junit-jupiter-params:jar:5.4.0:test
[INFO]    +- org.apiguardian:apiguardian-api:jar:1.0.0:test
[INFO]    \- org.junit.jupiter:junit-jupiter-api:jar:5.4.0:test
[INFO]       +- org.opentest4j:opentest4j:jar:1.1.1:test
[INFO]       \- org.junit.platform:junit-platform-commons:jar:1.4.0:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.423 s
[INFO] Finished at: 2022-07-24T18:25:41+07:00
[INFO] ------------------------------------------------------------------------

3. Jersey and HK2 dependency injection

3.1 Jersey uses HK2 as the dependency injection framework. The below are @Contract and @Service components, and later we will inject them into the Jersey application.

MessageService.java

package com.favtuts.service;

import org.jvnet.hk2.annotations.Contract;

@Contract
public interface MessageService {
  String getHello();
}

MessageServiceImpl.java

package com.favtuts.service;

import org.jvnet.hk2.annotations.Service;

@Service
public class MessageServiceImpl implements MessageService {

  @Override
  public String getHello() {
      return "Hello World Jersey from HK2";
  }

}

3.2 This Feature enables the auto-scanning components.

AutoScanFeature.java

package com.favtuts.config;

import jakarta.inject.Inject;
import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.Populator;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ClasspathDescriptorFileFinder;
import org.glassfish.hk2.utilities.DuplicatePostProcessor;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/* Auto scan the JAX-RS @Contract and @Service  */
public class AutoScanFeature implements Feature {

  @Inject
  ServiceLocator serviceLocator;

  @Override
  public boolean configure(FeatureContext context) {

      DynamicConfigurationService dcs =
              serviceLocator.getService(DynamicConfigurationService.class);
      Populator populator = dcs.getPopulator();
      try {
          // Populator - populate HK2 service locators from inhabitants files
          // ClasspathDescriptorFileFinder - find files from META-INF/hk2-locator/default
          populator.populate(
                  new ClasspathDescriptorFileFinder(this.getClass().getClassLoader()),
                  new DuplicatePostProcessor());

      } catch (IOException | MultiException ex) {
          Logger.getLogger(AutoScanFeature.class.getName()).log(Level.SEVERE, null, ex);
      }
      return true;
  }
}

Note
More Jersey and HK2 examples.

4. Jersey endpoints

The below is a Jersey application that exposed a few endpoints to produce different output in text format.

MyResource.java

package com.favtuts.resource;

import com.favtuts.service.MessageService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class MyResource {

  // DI via HK2
  @Inject
  private MessageService messageService;

  // output text
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String hello() {
      return "Jersey hello world example.";
  }

  // output text with argument
  @Path("/{name}")
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String hello(@PathParam("name") String name) {
      return "Jersey: hello " + name;
  }

  // for dependency injection
  @Path("/hk2")
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String helloHK2() {
      return messageService.getHello();
  }

}

5. Start Jersey application

There is no magic here; we need to manually configure the HTTP server and start and end the HTTP Server and the Jersey application. Read the code comments for self-explanatory.

MainApp.java

package com.favtuts;

import com.favtuts.config.AutoScanFeature;
import com.favtuts.resource.MyResource;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MainApp {

  private static final Logger LOGGER = Logger.getLogger(MainApp.class.getName());

  // we start at port 8080
  public static final String BASE_URI = "http://localhost:8080/";

  // Starts Grizzly HTTP server
  public static HttpServer startServer() {

      // scan packages
      final ResourceConfig config = new ResourceConfig();
      // config.packages(true, "com.favtuts");
      config.register(MyResource.class);

      // enable auto scan @Contract and @Service
      config.register(AutoScanFeature.class);

      LOGGER.info("Starting Server........");

      final HttpServer httpServer =
              GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), config);

      return httpServer;

  }

  public static void main(String[] args) {

      try {

          final HttpServer httpServer = startServer();

          // add jvm shutdown hook
          Runtime.getRuntime().addShutdownHook(new Thread(() -> {
              try {
                  System.out.println("Shutting down the application...");

                  httpServer.shutdownNow();

                  System.out.println("Done, exit.");
              } catch (Exception e) {
                  Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, e);
              }
          }));

          System.out.println(String.format("Application started.%nStop the application using CTRL+C"));

          // block and wait shut down signal, like CTRL+C
          Thread.currentThread().join();

      } catch (InterruptedException ex) {
          Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
      }

  }
}

6. Demo

6.1 Start the MainApp directly, or pack it into a single jar and run it.

Terminal

$ cd {project}

$ mvn package

$ java -jar target/jersey-hello-world-1.0.jar

Jun 17, 2021 3:34:56 PM com.mkyong.MainApp startServer
INFO: Starting Server........
Jun 17, 2021 3:34:57 PM org.glassfish.jersey.server.wadl.WadlFeature configure
WARNING: JAX-B API not found . WADL feature is disabled.
Jun 17, 2021 3:34:57 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Jun 17, 2021 3:34:57 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Application started.
Stop the application using CTRL+C

6.2 We can use a simple cURL to test the Jersey endpoints.

Terminal

$ curl http://localhost:8080/hello
Jersey hello world example.

$ curl http://localhost:8080/hello/favtuts
Jersey: hello favtuts

$ curl http://localhost:8080/hello/hk2
Hello World Jersey from HK2

$ curl -v http://localhost:8080/hello/hk2
*   Trying ::1...
* TCP_NODELAY set
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /hello/hk2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 27
<
Hello World Jersey from HK2

7. Jersey and unit test

A JAX-RS standard Client and WebTarget class to test the Jersey endpoints.

MyResourceTest.java

package com.favtuts;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;

import org.glassfish.grizzly.http.server.HttpServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

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

public class MyResourceTest {

  private static HttpServer httpServer;
  private static WebTarget target;

  @BeforeAll
  public static void beforeAllTests() {
      httpServer = MainApp.startServer();
      Client c = ClientBuilder.newClient();
      target = c.target(MainApp.BASE_URI);
  }

  @AfterAll
  public static void afterAllTests() {
      httpServer.stop();
  }

  @Test
  public void testHello() {
      String response = target.path("hello").request().get(String.class);
      assertEquals("Jersey hello world example.", response);
  }

  @Test
  public void testHelloName() {
      String response = target.path("hello/mkyong").request().get(String.class);
      assertEquals("Jersey: hello mkyong", response);
  }

  @Test
  public void testHelloHK2() {
      String response = target.path("hello/hk2").request().get(String.class);
      assertEquals("Hello World Jersey from HK2", response);
  }

}

8. Download Source Code

$ git clone https://github.com/favtuts/java-jax-rs-tutorials.git

$ cd jersey-hello-world-example

$ mvn clean package

$ java -jar target/jersey-hello-world-example-1.0.jar

9. References

Leave a Reply

Your email address will not be published. Required fields are marked *