Creating a RESTful web service

duration 30 minutes

Prerequisites:

Learn how to create a RESTful service with Jakarta Restful Web Services, JSON-B, and Open Liberty.

What you’ll learn

You will learn how to build and test a simple RESTful service with Jakarta Restful Web Services and JSON-B, which will expose the JVM’s system properties. The RESTful service responds to GET requests made to the http://localhost:9080/LibertyProject/system/properties URL.

The service responds to a GET request with a JSON representation of the system properties, where each property is a field in a JSON object, like this:

{
  "os.name":"Mac",
  "java.version": "1.8"
}

The design of an HTTP API is an essential part of creating a web application. The REST API is the go-to architectural style for building an HTTP API. The Jakarta Restful Web Services API offers functions to create, read, update, and delete exposed resources. The Jakarta Restful Web Services API supports the creation of RESTful web services that are performant, scalable, and modifiable.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-rest-intro.git
cd guide-rest-intro

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready:

The defaultServer server is ready to run a smarter planet.

Check out the service at the http://localhost:9080/LibertyProject/system/properties URL.

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Creating a RESTful application

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

Jakarta Restful Web Services defines two key concepts for creating REST APIs. The most obvious one is the resource itself, which is modelled as a class. The second is a RESTful application, which groups all exposed resources under a common path. You can think of the RESTful application as a wrapper for all of your resources.

Replace the SystemApplication class.
src/main/java/io/openliberty/guides/rest/SystemApplication.java

SystemApplication.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.rest;
13
14import jakarta.ws.rs.core.Application;
15import jakarta.ws.rs.ApplicationPath;
16
17// tag::applicationPath[]
18@ApplicationPath("system")
19// end::applicationPath[]
20// tag::systemApplication[]
21public class SystemApplication extends Application {
22
23}
24// end::systemApplication[]

The SystemApplication class extends the Application class, which associates all RESTful resource classes in the WAR file with this RESTful application. These resources become available under the common path that’s specified with the @ApplicationPath annotation. The @ApplicationPath annotation has a value that indicates the path in the WAR file that the RESTful application accepts requests from.

Creating the RESTful resource

In a RESTful application, a single class represents a single resource, or a group of resources of the same type. In this application, a resource might be a system property, or a set of system properties. A single class can easily handle multiple different resources, but keeping a clean separation between types of resources helps with maintainability in the long run.

Create the PropertiesResource class.
src/main/java/io/openliberty/guides/rest/PropertiesResource.java

PropertiesResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.rest;
13
14import java.util.Properties;
15
16import jakarta.ws.rs.GET;
17import jakarta.ws.rs.Path;
18import jakarta.ws.rs.Produces;
19import jakarta.ws.rs.core.MediaType;
20
21// tag::path[]
22@Path("properties")
23// end::path[]
24public class PropertiesResource {
25
26    // tag::get[]
27    @GET
28    // end::get[]
29    // tag::produces[]
30    @Produces(MediaType.APPLICATION_JSON)
31    // end::produces[]
32    public Properties getProperties() {
33        return System.getProperties();
34    }
35
36}

SystemApplication.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.rest;
13
14import jakarta.ws.rs.core.Application;
15import jakarta.ws.rs.ApplicationPath;
16
17// tag::applicationPath[]
18@ApplicationPath("system")
19// end::applicationPath[]
20// tag::systemApplication[]
21public class SystemApplication extends Application {
22
23}
24// end::systemApplication[]

The @Path annotation on the class indicates that this resource responds to the properties path in the RESTful Web Services application. The @ApplicationPath annotation in the SystemApplication class together with the @Path annotation in this class indicates that the resource is available at the system/properties path.

Jakarta Restful Web Services maps the HTTP methods on the URL to the methods of the class by using annotations. Your application uses the GET annotation to map an HTTP GET request to the system/properties path.

The @GET annotation on the method indicates that this method is called for the HTTP GET method. The @Produces annotation indicates the format of the content that is returned. The value of the @Produces annotation is specified in the HTTP Content-Type response header. This application returns a JSON structured. The desired Content-Type for a JSON response is application/json, with MediaType.APPLICATION_JSON instead of the String content type. Using a constant such as MediaType.APPLICATION_JSON is better because a spelling error results in a compile failure.

Jakarta Restful Web Services supports a number of ways to marshal JSON. The Jakarta Restful Web Services specification mandates JSON-Binding (JSON-B). The method body returns the result of System.getProperties(), which is of type java.util.Properties. The method is annotated with @Produces(MediaType.APPLICATION_JSON) so Jakarta Restful Web Services uses JSON-B to automatically convert the returned object to JSON data in the HTTP response.

Configuring Liberty

To get the service running, the Liberty server.xml configuration file needs to be correctly configured.

Replace the Liberty server.xml configuration file.
src/main/liberty/config/server.xml

server.xml

 1<server description="Intro REST Guide Liberty server">
 2  <!-- tag::featureManager[] -->
 3  <featureManager>
 4      <feature>restfulWS-3.1</feature>
 5      <feature>jsonb-3.0</feature>
 6  </featureManager>
 7  <!-- end::featureManager[] -->
 8
 9  <!-- tag::httpEndpoint[] -->
10  <httpEndpoint httpPort="${http.port}" httpsPort="${https.port}"
11                id="defaultHttpEndpoint" host="*" />
12  <!-- end::httpEndpoint[] -->
13  
14  <!-- tag::webApplication[] -->
15  <webApplication location="guide-rest-intro.war" contextRoot="${app.context.root}"/>
16  <!-- end::webApplication[] -->
17</server>

The configuration does the following actions:

  • Configures Liberty to enable Jakarta Restful Web Services. This is specified in the featureManager element.

  • Configures Liberty to resolve the HTTP port numbers from variables, which are then specified in the Maven pom.xml file. This is specified in the httpEndpoint element. Variables use the ${variableName} syntax.

  • Configures Liberty to run the produced web application on a context root specified in the pom.xml file. This is specified in the webApplication element.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<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/xsd/maven-4.0.0.xsd">
  3    <modelVersion>4.0.0</modelVersion>
  4
  5    <groupId>io.openliberty.guides</groupId>
  6    <artifactId>guide-rest-intro</artifactId>
  7    <version>1.0-SNAPSHOT</version>
  8    <packaging>war</packaging>
  9
 10    <properties>
 11        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 12        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 13        <maven.compiler.source>11</maven.compiler.source>
 14        <maven.compiler.target>11</maven.compiler.target>
 15        <!-- Liberty configuration -->
 16        <!-- tag::defaultHttpPort[] -->
 17        <liberty.var.http.port>9080</liberty.var.http.port>
 18        <!-- end::defaultHttpPort[] -->
 19        <!-- tag::defaultHttpsPort[] -->
 20        <liberty.var.https.port>9443</liberty.var.https.port>
 21        <!-- end::defaultHttpsPort[] -->
 22        <!-- tag::appContextRoot[] -->
 23        <liberty.var.app.context.root>LibertyProject</liberty.var.app.context.root>
 24        <!-- end::appContextRoot[] -->
 25    </properties>
 26
 27    <dependencies>
 28        <!-- Provided dependencies -->
 29        <dependency>
 30            <groupId>jakarta.platform</groupId>
 31            <artifactId>jakarta.jakartaee-api</artifactId>
 32            <version>10.0.0</version>
 33            <scope>provided</scope>
 34        </dependency>
 35        <dependency>
 36            <groupId>org.eclipse.microprofile</groupId>
 37            <artifactId>microprofile</artifactId>
 38            <version>6.1</version>
 39            <type>pom</type>
 40            <scope>provided</scope>
 41        </dependency>
 42        <!-- For tests -->
 43        <dependency>
 44            <groupId>org.junit.jupiter</groupId>
 45            <artifactId>junit-jupiter</artifactId>
 46            <version>5.10.1</version>
 47            <scope>test</scope>
 48        </dependency>
 49        <dependency>
 50            <groupId>org.jboss.resteasy</groupId>
 51            <artifactId>resteasy-client</artifactId>
 52            <version>6.2.7.Final</version>
 53            <scope>test</scope>
 54        </dependency>
 55        <dependency>
 56            <groupId>org.jboss.resteasy</groupId>
 57            <artifactId>resteasy-json-binding-provider</artifactId>
 58            <version>6.2.7.Final</version>
 59            <scope>test</scope>
 60        </dependency>
 61        <dependency>
 62            <groupId>org.eclipse</groupId>
 63            <artifactId>yasson</artifactId>
 64            <version>3.0.3</version>
 65            <scope>test</scope>
 66        </dependency>
 67    </dependencies>
 68
 69    <build>
 70        <finalName>${project.artifactId}</finalName>
 71        <plugins>
 72            <!-- Enable liberty-maven plugin -->
 73            <plugin>
 74                <groupId>io.openliberty.tools</groupId>
 75                <artifactId>liberty-maven-plugin</artifactId>
 76                <version>3.10</version>
 77            </plugin>
 78            <!-- Plugin to run functional tests -->
 79            <plugin>
 80                <groupId>org.apache.maven.plugins</groupId>
 81                <artifactId>maven-failsafe-plugin</artifactId>
 82                <version>3.2.5</version>
 83                <configuration>
 84                    <!-- tag::testsysprops[] -->
 85                    <systemPropertyVariables>
 86                        <http.port>${liberty.var.http.port}</http.port>
 87                        <context.root>${liberty.var.app.context.root}</context.root>
 88                    </systemPropertyVariables>
 89                    <!-- end::testsysprops[] -->
 90                </configuration>
 91            </plugin>
 92            <plugin>
 93                <groupId>org.apache.maven.plugins</groupId>
 94                <artifactId>maven-war-plugin</artifactId>
 95                <version>3.3.2</version>
 96            </plugin>
 97            <!-- Plugin to run unit tests -->
 98            <plugin>
 99                <groupId>org.apache.maven.plugins</groupId>
100                <artifactId>maven-surefire-plugin</artifactId>
101                <version>3.2.5</version>
102            </plugin>
103        </plugins>
104    </build>
105</project>

The variables that are being used in the server.xml file are provided by the properties set in the Maven pom.xml file. The properties must be formatted as liberty.var.variableName.

Running the application

You started the Open Liberty in dev mode at the beginning of the guide, so all the changes were automatically picked up.

Check out the service that you created at the http://localhost:9080/LibertyProject/system/properties URL.

Testing the service

You can test this service manually by starting Liberty and pointing a web browser at the http://localhost:9080/LibertyProject/system/properties URL. However, automated tests are a much better approach because they trigger a failure if a change introduces a bug. JUnit and the Jakarta Restful Web Services Client API provide a simple environment to test the application.

You can write tests for the individual units of code outside of a running Liberty instance, or they can be written to call the Liberty instance directly. In this example, you will create a test that does the latter.

Create the EndpointIT class.
src/test/java/it/io/openliberty/guides/rest/EndpointIT.java

EndpointIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package it.io.openliberty.guides.rest;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15
16import java.util.Properties;
17
18import jakarta.json.bind.Jsonb;
19import jakarta.json.bind.JsonbBuilder;
20import jakarta.ws.rs.client.Client;
21import jakarta.ws.rs.client.ClientBuilder;
22import jakarta.ws.rs.client.WebTarget;
23import jakarta.ws.rs.core.Response;
24
25import org.junit.jupiter.api.Test;
26
27public class EndpointIT {
28    private static final Jsonb JSONB = JsonbBuilder.create();
29    // tag::test[]
30    @Test
31    // end::test[]
32    public void testGetProperties() {
33        // tag::systemProperties[]
34        String port = System.getProperty("http.port");
35        String context = System.getProperty("context.root");
36        // end::systemProperties[]
37        String url = "http://localhost:" + port + "/" + context + "/";
38
39        // tag::clientSetup[]
40        Client client = ClientBuilder.newClient();
41        // end::clientSetup[]
42
43        // tag::target[]
44        WebTarget target = client.target(url + "system/properties");
45        // end::target[]
46        // tag::requestget[]
47        Response response = target.request().get();
48        // end::requestget[]
49
50        // tag::assertequals[]
51        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(),
52                     "Incorrect response code from " + url);
53        // end::assertequals[]
54
55        // tag::body[]
56        String json = response.readEntity(String.class);
57        Properties sysProps = JSONB.fromJson(json, Properties.class);
58
59        // tag::assertosname[]
60        assertEquals(System.getProperty("os.name"), sysProps.getProperty("os.name"),
61                     "The system property for the local and remote JVM should match");
62        // end::assertosname[]
63        // end::body[]
64        response.close();
65        client.close();
66    }
67}

This test class has more lines of code than the resource implementation. This situation is common. The test method is indicated with the @Test annotation.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<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/xsd/maven-4.0.0.xsd">
  3    <modelVersion>4.0.0</modelVersion>
  4
  5    <groupId>io.openliberty.guides</groupId>
  6    <artifactId>guide-rest-intro</artifactId>
  7    <version>1.0-SNAPSHOT</version>
  8    <packaging>war</packaging>
  9
 10    <properties>
 11        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 12        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 13        <maven.compiler.source>11</maven.compiler.source>
 14        <maven.compiler.target>11</maven.compiler.target>
 15        <!-- Liberty configuration -->
 16        <!-- tag::defaultHttpPort[] -->
 17        <liberty.var.http.port>9080</liberty.var.http.port>
 18        <!-- end::defaultHttpPort[] -->
 19        <!-- tag::defaultHttpsPort[] -->
 20        <liberty.var.https.port>9443</liberty.var.https.port>
 21        <!-- end::defaultHttpsPort[] -->
 22        <!-- tag::appContextRoot[] -->
 23        <liberty.var.app.context.root>LibertyProject</liberty.var.app.context.root>
 24        <!-- end::appContextRoot[] -->
 25    </properties>
 26
 27    <dependencies>
 28        <!-- Provided dependencies -->
 29        <dependency>
 30            <groupId>jakarta.platform</groupId>
 31            <artifactId>jakarta.jakartaee-api</artifactId>
 32            <version>10.0.0</version>
 33            <scope>provided</scope>
 34        </dependency>
 35        <dependency>
 36            <groupId>org.eclipse.microprofile</groupId>
 37            <artifactId>microprofile</artifactId>
 38            <version>6.1</version>
 39            <type>pom</type>
 40            <scope>provided</scope>
 41        </dependency>
 42        <!-- For tests -->
 43        <dependency>
 44            <groupId>org.junit.jupiter</groupId>
 45            <artifactId>junit-jupiter</artifactId>
 46            <version>5.10.1</version>
 47            <scope>test</scope>
 48        </dependency>
 49        <dependency>
 50            <groupId>org.jboss.resteasy</groupId>
 51            <artifactId>resteasy-client</artifactId>
 52            <version>6.2.7.Final</version>
 53            <scope>test</scope>
 54        </dependency>
 55        <dependency>
 56            <groupId>org.jboss.resteasy</groupId>
 57            <artifactId>resteasy-json-binding-provider</artifactId>
 58            <version>6.2.7.Final</version>
 59            <scope>test</scope>
 60        </dependency>
 61        <dependency>
 62            <groupId>org.eclipse</groupId>
 63            <artifactId>yasson</artifactId>
 64            <version>3.0.3</version>
 65            <scope>test</scope>
 66        </dependency>
 67    </dependencies>
 68
 69    <build>
 70        <finalName>${project.artifactId}</finalName>
 71        <plugins>
 72            <!-- Enable liberty-maven plugin -->
 73            <plugin>
 74                <groupId>io.openliberty.tools</groupId>
 75                <artifactId>liberty-maven-plugin</artifactId>
 76                <version>3.10</version>
 77            </plugin>
 78            <!-- Plugin to run functional tests -->
 79            <plugin>
 80                <groupId>org.apache.maven.plugins</groupId>
 81                <artifactId>maven-failsafe-plugin</artifactId>
 82                <version>3.2.5</version>
 83                <configuration>
 84                    <!-- tag::testsysprops[] -->
 85                    <systemPropertyVariables>
 86                        <http.port>${liberty.var.http.port}</http.port>
 87                        <context.root>${liberty.var.app.context.root}</context.root>
 88                    </systemPropertyVariables>
 89                    <!-- end::testsysprops[] -->
 90                </configuration>
 91            </plugin>
 92            <plugin>
 93                <groupId>org.apache.maven.plugins</groupId>
 94                <artifactId>maven-war-plugin</artifactId>
 95                <version>3.3.2</version>
 96            </plugin>
 97            <!-- Plugin to run unit tests -->
 98            <plugin>
 99                <groupId>org.apache.maven.plugins</groupId>
100                <artifactId>maven-surefire-plugin</artifactId>
101                <version>3.2.5</version>
102            </plugin>
103        </plugins>
104    </build>
105</project>

The test code needs to know some information about the application to make requests. The server port and the application context root are key, and are dictated by the Liberty’s configuration. While this information can be hardcoded, it is better to specify it in a single place like the Maven pom.xml file. Refer to the pom.xml file to see how the application information such as the http.port, https.port and app.context.root elements are provided in the file.

These Maven properties are then passed to the Java test program as the systemPropertyVariables element in the pom.xml file.

Getting the values to create a representation of the URL is simple. The test class uses the getProperty method to get the application details.

To call the RESTful service using the Jakarta Restful Web Services client, first create a WebTarget object by calling the target method that provides the URL. To cause the HTTP request to occur, the request().get() method is called on the WebTarget object. The get method call is a synchronous call that blocks until a response is received. This call returns a Response object, which can be inspected to determine whether the request was successful.

The first thing to check is that a 200 response was received. The JUnit assertEquals method can be used for this check.

Check the response body to ensure it returned the right information. The client and the server are running on the same machine so it is reasonable to expect that the system properties for the local and remote JVM would be the same. In this case, an assertEquals assertion is made so that the os.name system property for both JVMs is the same. You can write additional assertions to check for more values.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You will see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.rest.EndpointIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.884 sec - in it.io.openliberty.guides.rest.EndpointIT

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

To see whether the tests detect a failure, add an assertion that you know fails, or change the existing assertion to a constant value that doesn’t match the os.name system property.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

Great work! You’re done!

You just developed a RESTful service in Open Liberty by using Jakarta Restful Web Services and JSON-B.

Guide Attribution

Creating a RESTful web service by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star