Jakarta REST: Advanced Topics and an Example

Jakarta RESTful Web Services (Jakarta REST) is designed to make it easy to develop applications that use the REST architecture. This chapter describes advanced features of Jakarta REST. If you are new to Jakarta REST, see Building RESTful Web Services with Jakarta REST before you proceed with this chapter.

Jakarta REST is integrated with Jakarta Contexts and Dependency Injection (CDI), Jakarta Enterprise Beans technology, and Jakarta Servlet technology.

Annotations for Field and Bean Properties of Resource Classes

Jakarta REST annotations for resource classes let you extract specific parts or values from a Uniform Resource Identifier (URI) or request header.

Jakarta REST provides the annotations listed in Advanced Jakarta REST Annotations.

Advanced Jakarta REST Annotations
Annotation Description

@Context

Injects information into a class field, bean property, or method parameter

@CookieParam

Extracts information from cookies declared in the cookie request header

@FormParam

Extracts information from a request representation whose content type is application/x-www-form-urlencoded

@HeaderParam

Extracts the value of a header

@MatrixParam

Extracts the value of a URI matrix parameter

@PathParam

Extracts the value of a URI template parameter

@QueryParam

Extracts the value of a URI query parameter

Extracting Path Parameters

URI path templates are URIs with variables embedded within the URI syntax. The @PathParam annotation lets you use variable URI path fragments when you call a method.

The following code snippet shows how to extract the last name of an employee when the employee’s email address is provided:

@Path("/employees/{firstname}.{lastname}@{domain}.com")
public class EmpResource {

    @GET
    @Produces("text/xml")
    public String getEmployeelastname(@PathParam("lastname") String lastName) {
      ...
    }
}

In this example, the @Path annotation defines the URI variables (or path parameters) {firstname}, {lastname}, and {domain}. The @PathParam in the method parameter of the request method extracts the last name from the email address.

If your HTTP request is GET /employees/john.doe@example.com, the value “doe” is injected into {lastname}.

You can specify several path parameters in one URI.

You can declare a regular expression with a URI variable. For example, if it is required that the last name must consist only of lowercase and uppercase characters, you can declare the following regular expression:

@Path("/employees/{firstname}.{lastname[a-zA-Z]*}@{domain}.com")

If the last name does not match the regular expression, a 404 response is returned.

Extracting Query Parameters

Use the @QueryParam annotation to extract query parameters from the query component of the request URI.

For instance, to query all employees who have joined within a specific range of years, use a method signature like the following:

@Path("/employees/")
@GET
public Response getEmployees(
        @DefaultValue("2003") @QueryParam("minyear") int minyear,
        @DefaultValue("2013") @QueryParam("maxyear") int maxyear)
    {...}

This code snippet defines two query parameters, minyear and maxyear. The following HTTP request would query for all employees who have joined between 2003 and 2013:

GET /employees?maxyear=2013&minyear=2003

The @DefaultValue annotation defines a default value, which is to be used if no values are provided for the query parameters. By default, Jakarta REST assigns a null value for Object values and zero for primitive data types. You can use the @DefaultValue annotation to eliminate null or zero values and define your own default values for a parameter.

Extracting Form Data

Use the @FormParam annotation to extract form parameters from HTML forms. For example, the following form accepts the name, address, and manager’s name of an employee:

<FORM action="http://example.com/employees/" method="post">
  <p>
    <fieldset>
      Employee name: <INPUT type="text" name="empname" tabindex="1">
      Employee address: <INPUT type="text" name="empaddress" tabindex="2">
      Manager name: <INPUT type="text" name="managername" tabindex="3">
    </fieldset>
  </p>
</FORM>

Use the following code snippet to extract the manager name from this HTML form:

@POST
@Consumes("application/x-www-form-urlencoded")
public void post(@FormParam("managername") String managername) {
    // Store the value
    ...
}

To obtain a map of form parameter names to values, use a code snippet like the following:

@POST
@Consumes("application/x-www-form-urlencoded")
public void post(MultivaluedMap<String, String> formParams) {
    // Store the message
}

Extracting the Java Type of a Request or Response

The jakarta.ws.rs.core.Context annotation retrieves the Java types related to a request or response.

The jakarta.ws.rs.core.UriInfo interface provides information about the components of a request URI. The following code snippet shows how to obtain a map of query and path parameter names to values:

@GET
public String getParams(@Context UriInfo ui) {
    MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
    MultivaluedMap<String, String> pathParams = ui.getPathParameters();
}

The jakarta.ws.rs.core.HttpHeaders interface provides information about request headers and cookies. The following code snippet shows how to obtain a map of header and cookie parameter names to values:

@GET
public String getHeaders(@Context HttpHeaders hh) {
    MultivaluedMap<String, String> headerParams = hh.getRequestHeaders();
    MultivaluedMap<String, Cookie> pathParams = hh.getCookies();
}

Validating Resource Data with Bean Validation

Jakarta REST supports Bean Validation to verify Jakarta REST resource classes. This support consists of:

  • Adding constraint annotations to resource method parameters

  • Ensuring entity data is valid when the entity is passed in as a parameter

Using Constraint Annotations on Resource Methods

Bean Validation constraint annotations may be applied to parameters for a resource. The server will validate the parameters and either pass or throw a jakarta.validation.ValidationException.

@POST
@Path("/createUser")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createUser(@NotNull @FormParam("username") String username,
                       @NotNull @FormParam("firstName") String firstName,
                       @NotNull @FormParam("lastName") String lastName,
                       @Email @FormParam("email") String email) {
    ...
}

In the preceding example, the built-in constraint @NotNull is applied to the username, firstName, and lastName form fields. Another built-in constraint @Email validates that the email address supplied by the email form field is correctly formatted.

The constraints may also be applied to fields within a resource class.

@Path("/createUser")
public class CreateUserResource {
  @NotNull
  @FormParam("username")
  private String username;

  @NotNull
  @FormParam("firstName")
  private String firstName;

  @NotNull
  @FormParam("lastName")
  private String lastName;

  @Email
  @FormParam("email")
  private String email;

  ...
}

In the preceding example, the same constraints that were applied to the method parameters in the previous example are applied to the class fields. The behavior is the same in both examples.

Constraints may also be applied to a resource class’s JavaBeans properties by adding the constraint annotations to the getter method.

@Path("/createuser")
public class CreateUserResource {
  private String username;

  @FormParam("username")
  public void setUsername(String username) {
    this.username = username;
  }

  @NotNull
  public String getUsername() {
    return username;
  }
  ...
}

Constraints may also be applied at the resource class level. In the following example, @PhoneRequired is a user-defined constraint that ensures that a user enters at least one phone number. That is, either homePhone or mobilePhone can be null, but not both.

@Path("/createUser")
@PhoneRequired
public class CreateUserResource {
  @FormParam("homePhone")
  private Phone homePhone;

  @FormParam("mobilePhone")
  private Phone mobilePhone;
  ...
}

Validating Entity Data

Classes that contain validation constraint annotations may be used in method parameters in a resource class. To validate these entity classes, use the @Valid annotation on the method parameter. For example, the following class is a user-defined class containing both standard and user-defined validation constraints.

@PhoneRequired
public class User {
  @NotNull
  private String username;

  private Phone homePhone;

  private Phone mobilePhone;
  ...
}

This entity class is used as a parameter to a resource method.

@Path("/createUser")
public class CreateUserResource {
  ...
  @POST
  @Consumers(MediaType.APPLICATION_XML)
  public void createUser(@Valid User user) {
    ...
  }
  ...
}

The @Valid annotation ensures that the entity class is validated at runtime. Additional user-defined constraints can also trigger validation of an entity.

@Path("/createUser")
public class CreateUserResource {
  ...
  @POST
  @Consumers(MediaType.APPLICATION_XML)
  public void createUser(@ActiveUser User user) {
    ...
  }
  ...
}

In the preceding example, the user-defined @ActiveUser constraint is applied to the User class in addition to the @PhoneRequired and @NotNull constraints defined within the entity class.

If a resource method returns an entity class, validation may be triggered by applying the @Valid or any other user-defined constraint annotation to the resource method.

@Path("/getUser")
public class GetUserResource {
  ...
  @GET
  @Path("{username}")
  @Produces(MediaType.APPLICATION_XML)
  @ActiveUser
  @Valid
  public User getUser(@PathParam("username") String username) {
    // find the User
    return user;
  }
  ...
}

As in the previous example, the @ActiveUser constraint is applied to the returned entity class as well as the @PhoneRequired and @NotNull constraints defined within the entity class.

Validation Exception Handling and Response Codes

If a jakarta.validation.ValidationException or any subclass of ValidationException except ConstraintValidationException is thrown, the Jakarta REST runtime will respond to the client request with a 500 (Internal Server Error) HTTP status code.

If a ConstraintValidationException is thrown, the Jakarta REST runtime will respond to the client with one of the following HTTP status codes:

  • 500 (Internal Server Error) if the exception was thrown while validating a method return type

  • 400 (Bad Request) in all other cases

Subresources and Runtime Resource Resolution

You can use a resource class to process only a part of the URI request. A root resource can then implement subresources that can process the remainder of the URI path.

A resource class method that is annotated with @Path is either a subresource method or a subresource locator.

  • A subresource method is used to handle requests on a subresource of the corresponding resource.

  • A subresource locator is used to locate subresources of the corresponding resource.

Subresource Methods

A subresource method handles an HTTP request directly. The method must be annotated with a request method designator, such as @GET or @POST, in addition to @Path. The method is invoked for request URIs that match a URI template created by concatenating the URI template of the resource class with the URI template of the method.

The following code snippet shows how a subresource method can be used to extract the last name of an employee when the employee’s email address is provided:

@Path("/employeeinfo")
public class EmployeeInfo {

    public employeeinfo() {}

    @GET
    @Path("/employees/{firstname}.{lastname}@{domain}.com")
    @Produces("text/xml")
    public String getEmployeeLastName(@PathParam("lastname") String lastName) {
       ...
    }
}

The getEmployeeLastName method returns doe for the following GET request:

GET /employeeinfo/employees/john.doe@example.com

Subresource Locators

A subresource locator returns an object that will handle an HTTP request. The method must not be annotated with a request method designator. You must declare a subresource locator within a subresource class, and only subresource locators are used for runtime resource resolution.

The following code snippet shows a subresource locator:

// Root resource class
@Path("/employeeinfo")
public class EmployeeInfo {

    // Subresource locator: obtains the subresource Employee
    // from the path /employeeinfo/employees/{empid}
    @Path("/employees/{empid}")
    public Employee getEmployee(@PathParam("empid") String id) {
        // Find the Employee based on the id path parameter
        Employee emp = ...;
        ...
        return emp;
    }
}

// Subresource class
public class Employee {

    // Subresource method: returns the employee's last name
    @GET
    @Path("/lastname")
    public String getEmployeeLastName() {
        ...
        return lastName;
    }
}

In this code snippet, the getEmployee method is the subresource locator that provides the Employee object, which services requests for lastname.

If your HTTP request is GET /employeeinfo/employees/as209/, the getEmployee method returns an Employee object whose id is as209. At runtime, Jakarta REST sends a GET /employeeinfo/employees/as209/lastname request to the getEmployeeLastName method. The getEmployeeLastName method retrieves and returns the last name of the employee whose id is as209.

Integrating Jakarta REST with Jakarta Enterprise Beans Technology and CDI

Jakarta REST works with Jakarta Enterprise Beans technology and Jakarta Contexts and Dependency Injection (CDI).

In general, for Jakarta REST to work with enterprise beans, you need to annotate the class of a bean with @Path to convert it to a root resource class. You can use the @Path annotation with stateless session beans and singleton POJO beans.

The following code snippet shows a stateless session bean and a singleton bean that have been converted to Jakarta REST root resource classes.

@Stateless
@Path("stateless-bean")
public class StatelessResource {...}

@Singleton
@Path("singleton-bean")
public class SingletonResource {...}

Session beans can also be used for subresources.

Jakarta REST and CDI have slightly different component models. By default, Jakarta REST root resource classes are managed in the request scope, and no annotations are required for specifying the scope. CDI managed beans annotated with @RequestScoped or @ApplicationScoped can be converted to Jakarta REST resource classes.

The following code snippet shows a Jakarta REST resource class.

@Path("/employee/{id}")
public class Employee {
    public Employee(@PathParam("id") String id) {...}
}

@Path("{lastname}")
public final class EmpDetails {...}

The following code snippet shows this Jakarta REST resource class converted to a CDI bean. The beans must be proxyable, so the Employee class requires a nonprivate constructor with no parameters, and the EmpDetails class must not be final.

@Path("/employee/{id}")
@RequestScoped
public class Employee {
    public Employee() {...}

    @Inject
    public Employee(@PathParam("id") String id) {...}
}

@Path("{lastname}")
@RequestScoped
public class EmpDetails {...}

Conditional HTTP Requests

Jakarta REST provides support for conditional GET and PUT HTTP requests. Conditional GET requests help save bandwidth by improving the efficiency of client processing.

A GET request can return a Not Modified (304) response if the representation has not changed since the previous request. For example, a website can return 304 responses for all its static images that have not changed since the previous request.

A PUT request can return a Precondition Failed (412) response if the representation has been modified since the last request. The conditional PUT can help avoid the lost update problem.

Conditional HTTP requests can be used with the Last-Modified and ETag headers. The Last-Modified header can represent dates with granularity of one second.

@Path("/employee/{joiningdate}")
public class Employee {

    Date joiningdate;

    @GET
    @Produces("application/xml")
    public Employee(@PathParam("joiningdate") Date joiningdate,
                    @Context Request req,
                    @Context UriInfo ui) {

        this.joiningdate = joiningdate;
        ...
        this.tag = computeEntityTag(ui.getRequestUri());
        if (req.getMethod().equals("GET")) {
            Response.ResponseBuilder rb = req.evaluatePreconditions(tag);
            if (rb != null) {
                throw new WebApplicationException(rb.build());
            }
        }
    }
}

In this code snippet, the constructor of the Employee class computes the entity tag from the request URI and calls the request.evaluatePreconditions method with that tag. If a client request returns an If-none-match header with a value that has the same entity tag that was computed, evaluate.Preconditions returns a pre-filled-out response with a 304 status code and an entity tag set that may be built and returned.

Runtime Content Negotiation

The @Produces and @Consumes annotations handle static content negotiation in Jakarta REST. These annotations specify the content preferences of the server. HTTP headers such as Accept, Content-Type, and Accept-Language define the content negotiation preferences of the client.

For more details on the HTTP headers for content negotiation, see HTTP/1.1 - Content Negotiation (https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html).

The following code snippet shows the server content preferences:

@Produces("text/plain")
@Path("/employee")
public class Employee {

    @GET
    public String getEmployeeAddressText(String address) {...}

    @Produces("text/xml")
    @GET
    public String getEmployeeAddressXml(Address address) {...}
}

The getEmployeeAddressText method is called for an HTTP request that looks like the following:

GET /employee
Accept: text/plain

This will produce the following response:

500 Oracle Parkway, Redwood Shores, CA

The getEmployeeAddressXml method is called for an HTTP request that looks like the following:

GET /employee
Accept: text/xml

This will produce the following response:

<address street="500 Oracle Parkway, Redwood Shores, CA" country="USA"/>

With static content negotiation, you can also define multiple content and media types for the client and server.

@Produces("text/plain", "text/xml")

In addition to supporting static content negotiation, Jakarta REST also supports runtime content negotiation using the jakarta.ws.rs.core.Variant class and Request objects. The Variant class specifies the resource representation of content negotiation. Each instance of the Variant class may contain a media type, a language, and an encoding. The Variant object defines the resource representation that is supported by the server. The Variant.VariantListBuilder class is used to build a list of representation variants.

The following code snippet shows how to create a list of resource representation variants:

List<Variant> vs = Variant.mediatypes("application/xml", "application/json")
        .languages("en", "fr").build();

This code snippet calls the build method of the VariantListBuilder class. The VariantListBuilder class is invoked when you call the mediatypes, languages, or encodings methods. The build method builds a series of resource representations. The Variant list created by the build method has all possible combinations of items specified in the mediatypes, languages, and encodings methods.

In this example, the size of the vs object as defined in this code snippet is 4, and the contents are as follows:

[["application/xml","en"], ["application/json","en"],
    ["application/xml","fr"],["application/json","fr"]]

The jakarta.ws.rs.core.Request.selectVariant method accepts a list of Variant objects and chooses the Variant object that matches the HTTP request. This method compares its list of Variant objects with the Accept, Accept-Encoding, Accept-Language, and Accept-Charset headers of the HTTP request.

The following code snippet shows how to use the selectVariant method to select the most acceptable Variant from the values in the client request:

@GET
public Response get(@Context Request r) {
    List<Variant> vs = ...;
    Variant v = r.selectVariant(vs);
    if (v == null) {
        return Response.notAcceptable(vs).build();
    } else {
        Object rep = selectRepresentation(v);
        return Response.ok(rep, v);
    }
}

The selectVariant method returns the Variant object that matches the request or null if no matches are found. In this code snippet, if the method returns null, a Response object for a nonacceptable response is built. Otherwise, a Response object with an OK status and containing a representation in the form of an Object entity and a Variant is returned.

Using Jakarta REST with Jakarta XML Binding

Jakarta XML Binding is an XML-to-Java binding technology that simplifies the development of web services by enabling transformations between schema and Java objects and between XML instance documents and Java object instances. An XML schema defines the data elements and structure of an XML document. You can use Jakarta XML Binding APIs and tools to establish mappings between Java classes and XML schema. Jakarta XML Binding technology provides the tools that enable you to convert your XML documents to and from Java objects.

By using Jakarta XML Binding, you can manipulate data objects in the following ways.

  • You can start with an XML schema definition (XSD) and use xjc, the Jakarta XML Binding schema compiler tool, to create a set of Jakarta XML Binding annotated Java classes that map to the elements and types defined in the XSD schema.

  • You can start with a set of Java classes and use schemagen, the Jakarta XML Binding schema generator tool, to generate an XML schema.

  • Once a mapping between the XML schema and the Java classes exists, you can use the Jakarta XML Binding runtime to marshal and unmarshal your XML documents to and from Java objects and use the resulting Java classes to assemble a web services application.

XML is a common media format that RESTful services consume and produce. To deserialize and serialize XML, you can represent requests and responses by Jakarta XML Binding annotated objects. Your Jakarta REST application can use the Jakarta XML Binding objects to manipulate XML data. Jakarta XML Binding objects can be used as request entity parameters and response entities. The Jakarta REST runtime environment includes standard MessageBodyReader and MessageBodyWriter provider interfaces for reading and writing Jakarta XML Binding objects as entities.

With Jakarta REST, you enable access to your services by publishing resources. Resources are just simple Java classes with some additional Jakarta REST annotations. These annotations express the following:

  • The path of the resource (the URL you use to access it)

  • The HTTP method you use to call a certain method (for example, the GET or POST method)

  • The MIME type with which a method accepts or responds

As you define the resources for your application, consider the type of data you want to expose. You may already have a relational database that contains information you want to expose to users, or you may have static content that does not reside in a database but does need to be distributed as resources. Using Jakarta REST, you can distribute content from multiple sources. RESTful web services can use various types of input/output formats for request and response. The customer example, described in The customer Example Application, uses XML.

Resources have representations. A resource representation is the content in the HTTP message that is sent to, or returned from, the resource using the URI. Each representation a resource supports has a corresponding media type. For example, if a resource is going to return content formatted as XML, you can use application/xml as the associated media type in the HTTP message.Depending on the requirements of your application, resources can return representations in a preferred single format or in multiple formats. Jakarta REST provides @Consumes and @Produces annotations to declare the media types that are acceptable for a resource method to read and write.

Jakarta REST also maps Java types to and from resource representations using entity providers. A MessageBodyReader entity provider reads a request entity and deserializes the request entity into a Java type. A MessageBodyWriter entity provider serializes from a Java type into a response entity. For example, if a String value is used as the request entity parameter, the MessageBodyReader entity provider deserializes the request body into a new String. If a Jakarta XML Binding type is used as the return type on a resource method, the MessageBodyWriter serializes the Jakarta XML Binding object into a response body.

By default, the Jakarta REST runtime environment attempts to create and use a default JAXBContext class for Jakarta XML Binding classes. However, if the default JAXBContext class is not suitable, then you can supply a JAXBContext class for the application using a Jakarta REST ContextResolver provider interface.

The following sections explain how to use Jakarta XML Binding with Jakarta REST resource methods.

Using Java Objects to Model Your Data

If you do not have an XML schema definition for the data you want to expose, you can model your data as Java classes, add Jakarta XML Binding annotations to these classes, and use Jakarta XML Binding to generate an XML schema for your data. For example, if the data you want to expose is a collection of products and each product has an ID, a name, a description, and a price, you can model it as a Java class as follows:

@XmlRootElement(name="product")
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {

    @XmlElement(required=true)
    protected int id;
    @XmlElement(required=true)
    protected String name;
    @XmlElement(required=true)
    protected String description;
    @XmlElement(required=true)
    protected int price;

    public Product() {}

    // Getter and setter methods
    // ...
}

Run the Jakarta XML Binding schema generator on the command line to generate the corresponding XML schema definition:

schemagen Product.java

This command produces the XML schema as an .xsd file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="product" type="product"/>

    <xs:complexType name="product">
      <xs:sequence>
        <xs:element name="id" type="xs:int"/>
        <xs:element name="name" type="xs:string"/>
        <xs:element name="description" type="xs:string"/>
        <xs:element name="price" type="xs:int"/>
      </xs:sequence>
    <xs:complexType>
</xs:schema>

Once you have this mapping, you can create Product objects in your application, return them, and use them as parameters in Jakarta REST resource methods. The Jakarta REST runtime uses Jakarta XML Binding to convert the XML data from the request into a Product object and to convert a Product object into XML data for the response. The following resource class provides a simple example:

@Path("/product")
public class ProductService {
    @GET
    @Path("/get")
    @Produces("application/xml")
    public Product getProduct() {
        Product prod = new Product();
        prod.setId(1);
        prod.setName("Mattress");
        prod.setDescription("Queen size mattress");
        prod.setPrice(500);
        return prod;
    }

    @POST
    @Path("/create")
    @Consumes("application/xml")
    public Response createProduct(Product prod) {
        // Process or store the product and return a response
        // ...
    }
}

Some IDEs, such as NetBeans IDE, will run the schema generator tool automatically during the build process if you add Java classes that have Jakarta XML Binding annotations to your project. For a detailed example, see The customer Example Application. The customer example contains a more complex relationship between the Java classes that model the data, which results in a more hierarchical XML representation.

Starting from an Existing XML Schema Definition

If you already have an XML schema definition in an .xsd file for the data you want to expose, use the Jakarta XML Binding schema compiler tool. Consider this simple example of an .xsd file:

<xs:schema targetNamespace="http://xml.product"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified"
           xmlns:myco="http://xml.product">
  <xs:element name="product" type="myco:Product"/>
  <xs:complexType name="Product">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="description" type="xs:string"/>
      <xs:element name="price" type="xs:int"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Run the schema compiler tool on the command line as follows:

xjc Product.xsd

This command generates the source code for Java classes that correspond to the types defined in the .xsd file. The schema compiler tool generates a Java class for each complexType defined in the .xsd file. The fields of each generated Java class are the same as the elements inside the corresponding complexType, and the class contains getter and setter methods for these fields.

In this case, the schema compiler tool generates the classes product.xml.Product and product.xml.ObjectFactory. The Product class contains Jakarta XML Binding annotations, and its fields correspond to those in the .xsd definition:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Product", propOrder = {
    "id",
    "name",
    "description",
    "price"
})
public class Product {
    protected int id;
    @XmlElement(required = true)
    protected String name;
    @XmlElement(required = true)
    protected String description;
    protected int price;

    // Setter and getter methods
    // ...
}

You can create instances of the Product class from your application (for example, from a database). The generated class product.xml.ObjectFactory contains a method that allows you to convert these objects to Jakarta XML Binding elements that can be returned as XML inside Jakarta REST resource methods:

@XmlElementDecl(namespace = "http://xml.product", name = "product")
public JAXBElement<Product> createProduct(Product value) {
    return new JAXBElement<Product>(_Product_QNAME, Product.class, null, value);
}

The following code shows how to use the generated classes to return a Jakarta XML Binding element as XML in a Jakarta REST resource method:

@Path("/product")
public class ProductService {
    @GET
    @Path("/get")
    @Produces("application/xml")
    public JAXBElement<Product> getProduct() {
        Product prod = new Product();
        prod.setId(1);
        prod.setName("Mattress");
        prod.setDescription("Queen size mattress");
        prod.setPrice(500);
        return new ObjectFactory().createProduct(prod);
    }
}

For @POST and @PUT resource methods, you can use a Product object directly as a parameter. Jakarta REST maps the XML data from the request into a Product object.

@Path("/product")
public class ProductService {
    @GET
    // ...

    @POST
    @Path("/create")
    @Consumes("application/xml")
    public Response createProduct(Product prod) {
        // Process or store the product and return a response
        // ...
    }
}

Using JSON with Jakarta REST and Jakarta XML Binding

Jakarta REST can automatically read and write XML using Jakarta XML Binding, but it can also work with JSON data. JSON is a simple text-based format for data exchange derived from JavaScript. For the preceding examples, the XML representation of a product is

<?xml version="1.0" encoding="UTF-8"?>
<product>
  <id>1</id>
  <name>Mattress</name>
  <description>Queen size mattress</description>
  <price>500</price>
</product>

The equivalent JSON representation is

{
    "id":"1",
    "name":"Mattress",
    "description":"Queen size mattress",
    "price":500
}

You can add the format application/json or MediaType.APPLICATION_JSON to the @Produces annotation in resource methods to produce responses with JSON data:

@GET
@Path("/get")
@Produces({"application/xml","application/json"})
public Product getProduct() { ... }

In this example, the default response is XML, but the response is a JSON object if the client makes a GET request that includes this header:

Accept: application/json

The resource methods can also accept JSON data for Jakarta XML Binding annotated classes:

@POST
@Path("/create")
@Consumes({"application/xml","application/json"})
public Response createProduct(Product prod) { ... }

The client should include the following header when submitting JSON data with a POST request:

Content-Type: application/json

The customer Example Application

This section describes how to build and run the customer example application. This application is a RESTful web service that uses Jakarta XML Binding to perform the create, read, update, delete (CRUD) operations for a specific entity.

The customer sample application is in the jakartaee-examples/tutorial/rest/customer/ directory. See Using the Tutorial Examples, for basic information on building and running sample applications.

Overview of the customer Example Application

The source files of this application are at jakartaee-examples/tutorial/rest/customer/src/main/java/. The application has three parts.

  • The Customer and Address entity classes. These classes model the data of the application and contain Jakarta XML Binding annotations.

  • The CustomerService resource class. This class contains Jakarta REST resource methods that perform operations on Customer instances represented as XML or JSON data using Jakarta XML Binding. See The CustomerService Class for details.

  • The CustomerBean session bean that acts as a backing bean for the web client. CustomerBean uses the Jakarta REST client API to call the methods of CustomerService.

The customer example application shows you how to model your data entities as Java classes with Jakarta XML Binding annotations.

The Customer and Address Entity Classes

The following class represents a customer’s address:

@Entity
@Table(name="CUSTOMER_ADDRESS")
@XmlRootElement(name="address")
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @XmlElement(required=true)
    protected int number;

    @XmlElement(required=true)
    protected String street;

    @XmlElement(required=true)
    protected String city;

    @XmlElement(required=true)
    protected String province;

    @XmlElement(required=true)
    protected String zip;

    @XmlElement(required=true)
    protected String country;

    public Address() { }

    // Getter and setter methods
    // ...
}

The @XmlRootElement(name="address") annotation maps this class to the address XML element. The @XmlAccessorType(XmlAccessType.FIELD) annotation specifies that all the fields of this class are bound to XML by default. The @XmlElement(required=true) annotation specifies that an element must be present in the XML representation.

The following class represents a customer:

@Entity
@Table(name="CUSTOMER_CUSTOMER")
@NamedQuery(
    name="findAllCustomers",
    query="SELECT c FROM Customer c " +
          "ORDER BY c.id"
)
@XmlRootElement(name="customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @XmlAttribute(required=true)
    protected int id;

    @XmlElement(required=true)
    protected String firstname;

    @XmlElement(required=true)
    protected String lastname;

    @XmlElement(required=true)
    @OneToOne
    protected Address address;

    @XmlElement(required=true)
    protected String email;

    @XmlElement (required=true)
    protected String phone;

    public Customer() {...}

    // Getter and setter methods
    // ...
}

The Customer class contains the same Jakarta XML Binding annotations as the previous class, except for the @XmlAttribute(required=true) annotation, which maps a property to an attribute of the XML element representing the class.

The Customer class contains a property whose type is another entity, the Address class. This mechanism allows you to define in Java code the hierarchical relationships between entities without having to write an .xsd file yourself.

Jakarta XML Binding generates the following XML schema definition for the two preceding classes:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="address" type="address"/>
  <xs:element name="customer" type="customer"/>

  <xs:complexType name="address">
    <xs:sequence>
      <xs:element name="id" type="xs:long" minOccurs="0"/>
      <xs:element name="number" type="xs:int"/>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city" type="xs:string"/>
      <xs:element name="province" type="xs:string"/>
      <xs:element name="zip" type="xs:string"/>
      <xs:element name="country" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="customer">
    <xs:sequence>
      <xs:element name="firstname" type="xs:string"/>
      <xs:element name="lastname" type="xs:string"/>
      <xs:element ref="address"/>
      <xs:element name="email" type="xs:string"/>
      <xs:element name="phone" type="xs:string"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:int" use="required"/>
  </xs:complexType>
</xs:schema>

The CustomerService Class

The CustomerService class has a createCustomer method that creates a customer resource based on the Customer class and returns a URI for the new resource.

@Stateless
@Path("/Customer")
public class CustomerService {
    public static final Logger logger =
            Logger.getLogger(CustomerService.class.getCanonicalName());
    @PersistenceContext
    private EntityManager em;
    private CriteriaBuilder cb;

    @PostConstruct
    private void init() {
        cb = em.getCriteriaBuilder();
    }
    ...
    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response createCustomer(Customer customer) {

        try {
            long customerId = persist(customer);
            return Response.created(URI.create("/" + customerId)).build();
        } catch (Exception e) {
            logger.log(Level.SEVERE,
                    "Error creating customer for customerId {0}. {1}",
                    new Object[]{customer.getId(), e.getMessage()});
            throw new WebApplicationException(e,
                    Response.Status.INTERNAL_SERVER_ERROR);
        }
    }
    ...
    private long persist(Customer customer) {
        try {
            Address address = customer.getAddress();
            em.persist(address);
            em.persist(customer);
        } catch (Exception ex) {
            logger.warning("Something went wrong when persisting the customer");
        }
        return customer.getId();
    }
}

The response returned to the client has a URI to the newly created resource. The return type is an entity body mapped from the property of the response with the status code specified by the status property of the response. The WebApplicationException is a RuntimeException that is used to wrap the appropriate HTTP error status code, such as 404, 406, 415, or 500.

The @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) and @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) annotations set the request and response media types to use the appropriate MIME client. These annotations can be applied to a resource method, a resource class, or even an entity provider. If you do not use these annotations, Jakarta REST allows the use of any media type ("*/*").

The following code snippet shows the implementation of the getCustomer and findbyId methods. The getCustomer method uses the @Produces annotation and returns a Customer object, which is converted to an XML or JSON representation depending on the Accept: header specified by the client.

    @GET
    @Path("{id}")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Customer getCustomer(@PathParam("id") String customerId) {
        Customer customer = null;

        try {
            customer = findById(customerId);
        } catch (Exception ex) {
            logger.log(Level.SEVERE,
                    "Error calling findCustomer() for customerId {0}. {1}",
                    new Object[]{customerId, ex.getMessage()});
        }
        return customer;
    }
    ...
    private Customer findById(String customerId) {
        Customer customer = null;
        try {
            customer = em.find(Customer.class, customerId);
            return customer;
        } catch (Exception ex) {
            logger.log(Level.WARNING,
                    "Couldn't find customer with ID of {0}", customerId);
        }
        return customer;
    }

Using the Jakarta REST Client in the CustomerBean Classes

Use the Jakarta REST Client API to write a client for the customer example application.

The CustomerBean enterprise bean class calls the Jakarta REST Client API to test the CustomerService web service:

@Named
@Stateless
public class CustomerBean {
    protected Client client;
    private static final Logger logger =
            Logger.getLogger(CustomerBean.class.getName());

    @PostConstruct
    private void init() {
        client = ClientBuilder.newClient();
    }

    @PreDestroy
    private void clean() {
        client.close();
    }

    public String createCustomer(Customer customer) {
        if (customer == null) {
            logger.log(Level.WARNING, "customer is null.");
            return "customerError";
        }
        String navigation;
        Response response =
                client.target("http://localhost:8080/customer/webapi/Customer")
                .request(MediaType.APPLICATION_XML)
                .post(Entity.entity(customer, MediaType.APPLICATION_XML),
                        Response.class);
        if (response.getStatus() == Status.CREATED.getStatusCode()) {
            navigation = "customerCreated";
        } else {
            logger.log(Level.WARNING, "couldn''t create customer with " +
                    "id {0}. Status returned was {1}",
                    new Object[]{customer.getId(), response.getStatus()});
            navigation = "customerError";
        }
        return navigation;
    }

    public String retrieveCustomer(String id) {
        String navigation;
        Customer customer =
                client.target("http://localhost:8080/customer/webapi/Customer")
                .path(id)
                .request(MediaType.APPLICATION_XML)
                .get(Customer.class);
        if (customer == null) {
            navigation = "customerError";
        } else {
            navigation = "customerRetrieved";
        }
        return navigation;
    }

    public List<Customer> retrieveAllCustomers() {
        List<Customer> customers =
                client.target("http://localhost:8080/customer/webapi/Customer")
                .path("all")
                .request(MediaType.APPLICATION_XML)
                .get(new GenericType<List<Customer>>() {});
        return customers;
    }
}

This client uses the POST and GET methods.

All of these HTTP status codes indicate success: 201 for POST, 200 for GET, and 204 for DELETE. For details about the meanings of HTTP status codes, see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.

Running the customer Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the customer application.

To Build, Package, and Deploy the customer Example Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/rest
  4. Select the customer folder.

  5. Click Open Project.

  6. In the Projects tab, right-click the customer project and select Build.

    This command builds and packages the application into a WAR file, customer.war, located in the target directory. Then, the WAR file is deployed to GlassFish Server.

  7. Open the web client in a browser at the following URL:

    http://localhost:8080/customer/

    The web client allows you to create and view customers.

To Build, Package, and Deploy the customer Example Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/rest/customer/
  3. Enter the following command:

    mvn install

    This command builds and packages the application into a WAR file, customer.war, located in the target directory. Then, the WAR file is deployed to GlassFish Server.

  4. Open the web client in a browser at the following URL:

    http://localhost:8080/customer/

    The web client allows you to create and view customers.