JAX-RS, Jakarta RESTful Web Services, is an API for creating RESTful Java applications and Jersey is the reference implementation, Happy learning.
Resources are POJOs annotated with the Path annotation. Here is a resource that does nothing:
@Path("resources")
public class MyResource {
}
Paths can have variables embed in the URIs, which makes them powerful. Following is an example where variable name
is retrieved from the path:
@Path("resources")
public class MyResource {
@GET
@Path("greet/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String hello(@PathParam("name") String name) {
return "Hello " + name;
}
}
@Path("greet/{username: [a-zA-Z]*}")
@Path
value may or may not begin or end with a /
, it makes no difference.Methods in resource classes are annotated with GET
, POST
, DELETE
and so on for finding the matching method to accept the request. The example above is an example of a GET
endpoint.
Both annotations, Produces and Consumes can be applied at class and method levels, and used with a MediaType. These annotations can be used to narrow down the matching of methods and transforing DTOs to appropriate types by an appropriate MessageBodyWriter implementation or a MessageBodyReader implementation.
The following example works with XML out of the box with Jersey, without the need of any additional libraries.
Considering the following dependencies:
<dependencies>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
A very simple Employee
modal:
@XmlRootElement
@Getter
@Setter
public class Employee {
String name;
}
and an EmployeeResource
:
@Path("employee")
public class EmployeeResource {
private static Employee onlyEmployee;
@POST
@Consumes(MediaType.APPLICATION_XML)
public void addEmployee(Employee employee) {
onlyEmployee = employee;
}
@GET
@Produces(MediaType.APPLICATION_XML)
public Employee getEmployee() {
return onlyEmployee;
}
}
This works out of the box, since it seems like jersey-server
has a dependency to jersey-media-jaxb
which provides MessageBodyReader
s and MessageBodyWriter
s for XML that implements JAXB. When I exclude this dependency:
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>2.29.1</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>
</exclusion>
</exclusions>
</dependency>
I started getting the following error:
MessageBodyWriter not found for media type=application/xml
For whatever reason, Jersey seems to not include any writers that would work with JSON out of the box. However, once the following dependency is included:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.29.1</version>
<scope>runtime</scope>
</dependency>
the very same example starts working with JSON format by changing the MediaType to (MediaType.APPLICATION_JSON)
. At this point the XmlRootElement
annotation is also not needed anymore.
There is a lot more going on with MediaTypes, such as being able to consume or produce different MediaTypes from a single method, based on the preference of the client. For further details please see the official documentation.
QueryParam annotation can be used to capture, well the query parameters from the URI. Here is an example:
@Path("query")
public class QueryResource {
private static final String JOHN_DOE = "John Doe";
@GET
@Produces(MediaType.TEXT_PLAIN)
public String greet(@QueryParam("name") @DefaultValue(JOHN_DOE) String s) {
return greetPerson(s.isEmpty() ? JOHN_DOE : s);
}
private String greetPerson(String s) {
return String.format("Greetings %s\n", s);
}
}
and a few runs:
kt$ curl -i http://localhost:8080/api/query?name=Koray
# HTTP/1.1 200 OK
# Date: Sun, 24 Nov 2019 02:52:26 GMT
# Content-Type: text/plain
# Content-Length: 16
# Server: Jetty(9.4.22.v20191022)
#
# Greetings Koray
kt$ curl -i http://localhost:8080/api/query?name=
# HTTP/1.1 200 OK
# Date: Sun, 24 Nov 2019 02:52:50 GMT
# Content-Type: text/plain
# Content-Length: 19
# Server: Jetty(9.4.22.v20191022)
# Greetings John Doe
kt$ curl -i http://localhost:8080/api/query
# HTTP/1.1 200 OK
# Date: Sun, 24 Nov 2019 02:52:57 GMT
# Content-Type: text/plain
# Content-Length: 19
# Server: Jetty(9.4.22.v20191022)
#
# Greetings John Doe
The official Jersey documentation has very clear and detailed information on the rest of the lesser used possibitiles such as MatrixParam
, HeadParam
, CookieParam
, FormParam
, and my personal favorite BeanParam.
Sub-resources can be used to further organize the design, if there are several method-level resources in a resource class that all are a part of a particular path.
@Path("myResource")
public class MyResource {
@Path("sub")
public MySubResource greet() {
return new MySubResource();
}
}
public class MySubResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from SubResource!\n";
}
}
And here is a sample invocation:
kt$ curl -i http://localhost:8080/api/myResource/sub
# HTTP/1.1 200 OK
# Date: Sun, 24 Nov 2019 03:11:22 GMT
# Content-Type: text/plain
# Content-Length: 24
# Server: Jetty(9.4.22.v20191022)
#
# Hello from SubResource!
So far we have been returning 200 OK
in our responses, but we can do better. Following is an example that returns a 201 Created
:
@Path("employee")
public class EmployeeResource {
static List<Employee> employees = new ArrayList<>();
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("{name}")
public Response createEmployee(@PathParam("name") String name) {
Employee e = new Employee(employees.size() + 1, name);
employees.add(e);
return Response.status(Response.Status.CREATED).entity(e).build();
}
}
And an example invocation:
kt$ curl -i -X POST http://localhost:8080/api/employee/koray
# HTTP/1.1 201 Created
# Date: Sun, 24 Nov 2019 23:18:05 GMT
# Content-Type: application/json
# Content-Length: 23
# Server: Jetty(9.4.22.v20191022)
#
# {"id":1,"name":"koray"}
Response building provides other functionality such as adding header values in responses, setting the entity tag and last modified date of the representation.
The Response class has some shortcuts, here is an example:
@Path("employee")
public class EmployeeResource {
static List<Employee> employees = new ArrayList<>();
@POST
@Path("{name}")
public Response createEmployee(@PathParam("name") String name,
@Context UriInfo uriInfo) {
Employee e = new Employee(employees.size() + 1, name);
employees.add(e);
UriBuilder path = uriInfo
.getBaseUriBuilder()
.path(EmployeeResource.class)
.path(valueOf(e.getId()));
return Response.created(path.build()).build();
}
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
// We could have returned the Employee directly instead of a Response
// The default code is 200 already
public Response employee(@PathParam("id") int id) {
return Response
.ok(employees.get(id - 1))
.build();
}
}
with a sample run:
kt$ curl -i -X POST http://localhost:8080/api/employee/koray
# HTTP/1.1 201 Created
# Date: Mon, 25 Nov 2019 02:21:45 GMT
# Location: http://localhost:8080/api/employee/1
# Content-Length: 0
# Server: Jetty(9.4.22.v20191022)
kt$ curl -i http://localhost:8080/api/employee/1
# HTTP/1.1 200 OK
# Date: Mon, 25 Nov 2019 02:21:54 GMT
# Content-Type: application/json
# Content-Length: 23
# Server: Jetty(9.4.22.v20191022)
# {"id":1,"name":"koray"}
Things to note in the example above:
Response
class, passing in the path the resource that can be retrieved from.
Location
header in the response automatically.Make sure to check other shortcuts such as: accepted and noContent.
JAX-RS also provides an API for the client side, i.e. for making requests. The following example demonstrates several features of JAX-RS in client side including:
WebTarget
s by chaining path
calls.POST
request with an entity in the body.GET
request and declaring the accepted MediaType
.resolveTemplate
.public class RestApiClient {
static String BASE_URL = "http://localhost:8080/api";
static WebTarget BASE_TARGET = ClientBuilder.newClient().target(BASE_URL);
static WebTarget MESSAGES = BASE_TARGET.path("messages");
static WebTarget BY_ID = MESSAGES.path("{id}");
public static void main(String[] args) {
Message message = new Message();
message.setAuthor("koraytugay");
message.setContent("From JAX-RS client!");
Response response = MESSAGES.request().post(Entity.json(message));
response = MESSAGES.request(MediaType.APPLICATION_JSON).get();
// Handling Generic Types, such as List<Message>
List<Message> messages =
response.readEntity(new GenericType<List<Message>>() {});
// resolveTemplate example
message = BY_ID.resolveTemplate("id", 1)
.request(MediaType.APPLICATION_JSON)
.get(Message.class);
}
}
JAX-RS also has a concept that is called invocations: where you prepare an object that is ready to make the request, that includes all the requiered information, which can be invoked at any time.
Here is an example:
public Invocation getMessageInvocation(int id) {
Invocation byId = BY_ID.resolveTemplate("id", 1)
.request(MediaType.APPLICATION_JSON)
.buildGet();
return byId; // Call anytime with byId.invoke();
}
I have examples to above topics in JAX-RS Playground listed in the References section.