Unlike SOAP and the WS- stack, which are specified as W3C standards, REST is really a set of principles for designing and using web-based interface. REST / RESTful applications rely heavily on other standards:
HTTP
URI, URL
XML, JSON, HTML, GIF, JPEG, and so forth (resource representations)
The role of JAX-RS (Java API for RESTful Web Services) is to provide APIs that support building RESTful services. However, JAX-RS is just one way of doing this. RESTful services can be implemented other ways in Java, and (indeed) in many other programming languages.
First of all for a JAX-RS application must be set a base URI from which all the resources will be available.
For that purpose the javax.ws.rs.core.Application
class must be extended and annotated with the javax.ws.rs.ApplicationPath
annotation. The annotation accepts a string argument which defines the base URI.
@ApplicationPath(JaxRsActivator.ROOT_PATH)
public class JaxRsActivator extends Application {
/**
* JAX-RS root path.
*/
public static final String ROOT_PATH = "/api";
}
Resources are simple POJO classes which are annotated with the @Path
annotation.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("/hello")
public class HelloWorldResource {
public static final String MESSAGE = "Hello StackOverflow!";
@GET
@Produces("text/plain")
public String getHello() {
return MESSAGE;
}
}
When a HTTP GET
request is sent to /hello
, the resource responds with a Hello StackOverflow!
message.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("/hello")
public class HelloWorldResource {
public static final String MESSAGE = "Hello World!";
@GET
@Produces("text/plain")
public String getHello() {
return MESSAGE;
}
@GET
@Path("/{letter}")
@Produces("text/plain")
public String getHelloLetter(@PathParam("letter") int letter){
if (letter >= 0 && letter < MESSAGE.length()) {
return MESSAGE.substring(letter, letter + 1);
} else {
return "";
}
}
}
GET
without a parameter gives all content ("Hello World!") and GET
with path parameter gives the specific letter out of that String.
Some examples:
$ curl http://localhost/hello Hello World!
$ curl http://localhost/hello/0 H
$ curl http://localhost/hello/4 o
Note: if you leave out the method-type annotation (e.g. the @GET
above), a request method defaults to being a GET request handler.
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
@Path("hello")
public class HelloWorldResource {
private String message = "Hello StackOverflow!";
@GET
@Produces("text/plain")
public String getHello() {
return message;
}
@DELETE
public Response deleteMessage() {
message = null;
return Response.noContent().build();
}
}
Consume it with curl:
$ curl http://localhost/hello
Hello StackOverflow!
$ curl -X "DELETE" http://localhost/hello
$ curl http://localhost/hello
null
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
@Path("hello")
public class HelloWorldResource {
@POST
@Path("/receiveParams")
public Response receiveHello(@FormParam("name") String name, @FormParam("message") String message) {
//process parameters
return Response.status(200).build();
}
@POST
@Path("/saveObject")
@Consumes("application/json")
public Response saveMessage(Message message) {
//process message json
return Response.status(200).entity("OK").build();
}
}
First method can be invoked through HTML form submission by sending captured input parameters. Form submit action should point to -
/hello/receiveParams
Second method requires Message POJO with getters/setters. Any REST client can call this method with JSON input as -
{"sender":"someone","message":"Hello SO!"}
POJO should have the same property as JSON to make serialization work.
public class Message {
String sender;
String message;
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException exception) {
return Response.serverError().entity("Invalid input: " + exception.getMessage()).build();
}
}
This exception mapper will catch all IllegalArgumentExceptions thrown in the application, and show the user a clear message instead of a stacktrace.
In order to get information about the URI the user agent used to access your resource, you can use the @Context
parameter annotation with a UriInfo
parameter. The UriInfo
object has a few methods that can be used to get different parts of the URI.
//server is running on https://localhost:8080,
// webapp is at /webapp, servlet at /webapp/servlet
@Path("class")
class Foo {
@GET
@Path("resource")
@Produces(MediaType.TEXT_PLAIN)
public Response getResource(@Context UriInfo uriInfo) {
StringBuilder sb = new StringBuilder();
sb.append("Path: " + uriInfo.getPath() + "\n");
sb.append("Absolute Path: " + uriInfo.getAbsolutePath() + "\n");
sb.append("Base URI: " + uriInfo.getBaseUri() + "\n");
sb.append("Request URI: " + uriInfo.getRequestUri() + "\n");
return Response.ok(sb.toString()).build();
}
}
output of a GET to https://localhost:8080/webapp/servlet/class/resource
:
Path: class/resource
Absolute Path: https://localhost:8080/webapp/servlet/class/resource#
Base URI: https://localhost:8080/webapp/servlet/
Request URI: https://localhost:8080/webapp/servlet/class/resource
Sometimes for organizational or other reasons it makes sense to have your top level resource return a sub-resource that would look like this. (Your sub-resource does not need to be an inner class)
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("items")
public class ItemsResource {
@Path("{id}")
public String item(@PathParam("id") String id) {
return new ItemSubResource(id);
}
public static class ItemSubResource {
private final String id;
public ItemSubResource(String id) {
this.id = id;
}
@GET
@Produces("text/plain")
public Item item() {
return "The item " + id;
}
}
}
This is an example of how to implement custom parameter converters for JAX-RS endpoints. The example shows two classes from Java 8's java.time
library.
@Provider
public class ParamConverters implements ParamConverterProvider {
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType,
Type genericType,
Annotation[] annotations)
{
if (rawType == LocalDate.class)
return (ParamConverter<T>) new ParamConverter<LocalDate>() {
@Override
public LocalDate fromString(String value) {
return LocalDate.parse(value);
}
@Override
public String toString(LocalDate value) {
return null;
}
};
else if (rawType == MonthDay.class)
return (ParamConverter<T>) new ParamConverter<MonthDay>() {
@Override
public MonthDay fromString(String value) {
int[] ddmm = Arrays.stream(value.split("/"))
.mapToInt(Integer::parseInt)
.toArray();
return MonthDay.of(ddmm[1], ddmm[0]);
}
@Override
public String toString(MonthDay value) {
return null;
}
};
return null;
}
}
Name binding is a concept that allows to say to a JAX-RS runtime that a specific filter or interceptor will be executed only for a specific resource method. When a filter or an interceptor is limited only to a specific resource method we say that it is name-bound. Filters and interceptors that do not have such a limitation are called global.
Filters or interceptors can be assigned to a resource method using the @NameBinding
annotation. This annotation is used as meta annotation for other user implemented annotations that are applied to a providers and resource methods. See the following example:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
The example above defines a new @Compress
annotation which is a name binding annotation as it is annotated with @NameBinding
. The @Compress
annotation can be used to bind filters and interceptor to endpoints.
Consider you have an interceptor that performs GZIP compression and you want to bind such interceptor to a resource method. To do it, annotate both the resource method and the interceptor, as following:
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
@Compress
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
The @Compress
is applied on the resource method getVeryLongString()
and on the interceptor GZIPWriterInterceptor
. The interceptor will be executed only if any resource method with such a annotation will be executed.
In above example, the interceptor will be executed only for the getVeryLongString()
method. The interceptor will not be executed for method getHello()
. In this example the reason is probably clear. We would like to compress only long data and we do not need to compress the short response of "Hello World!"
.
Name binding can be applied on a resource class. In the example HelloWorldResource
would be annotated with @Compress
. This would mean that all resource methods will use compression in this case.
Note that global filters are executed always, so even for resource methods which have any name binding annotations.