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.
Annotation | Description |
---|---|
|
Injects information into a class field, bean property, or method parameter |
|
Extracts information from cookies declared in the cookie request header |
|
Extracts information from a request representation whose content type is |
|
Extracts the value of a header |
|
Extracts the value of a URI matrix parameter |
|
Extracts the value of a URI template parameter |
|
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
orPOST
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
andAddress
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 onCustomer
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 ofCustomerService
.
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
-
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
-
From the File menu, choose Open Project.
-
In the Open Project dialog box, navigate to:
jakartaee-examples/tutorial/rest
-
Select the
customer
folder. -
Click Open Project.
-
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 thetarget
directory. Then, the WAR file is deployed to GlassFish Server. -
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
-
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
-
In a terminal window, go to:
jakartaee-examples/tutorial/rest/customer/
-
Enter the following command:
mvn install
This command builds and packages the application into a WAR file,
customer.war
, located in thetarget
directory. Then, the WAR file is deployed to GlassFish Server. -
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.