JAX-RS Server API Snippets

September 28th, 2014 by

Because a lot of my current projects are using JAX-RS in different versions I’d like to write down and share some frequently used snippets for implementing RESTful web-services with the JAX-RS specification here.

 

Using RegEx in Path Expressions

Sometimes we need to extract multiple parameters from a path expression e.g. in the following example where year, month and day are fragments if the path.

@GET
@Path("/orders/{year:\\d{4}}-{month:\\d{2}}-{day:\\d{2}}")
@Produces(MediaType.TEXT_PLAIN)
public Response getOrders(@PathParam("year") final int year, @PathParam("month") final int month, @PathParam("day") final int day) {
	return Response.ok("Year: " + year + ", month: " + month + ", day: " + day).build();
}

When we’re calling the method e.g. using curl, we’re getting the expected result:

$ curl -XGET http://localhost:8080/app/rs/orders/2004-12-24
Year: 2004, month: 12, day: 24%

Using Default Parameters

Instead of handling expected values manually we may use a simply annotation instead:

@GET
public Response getUsers(@DefaultValue("10") @QueryParam("limit") int limit){
	// ...
}

Handling and Mapping Exceptions

Sometimes we want to modify exceptions being passed to the client, filter the payload, set a specific HTTP status for the response:

So first of all let’s say we have the following exception:

class CustomException extends Exception {}

Then the following provider allows us to pass the exception and set the HTTP response status to 400 (BAD_REQUEST)

@Provider
public class ConcurrentEntityModificationExceptionMapper implements ExceptionMapper<CustomException> {
 
	@Override
	public Response toResponse(final CustomException e) {
		return Response.status(Status.BAD_REQUEST).entity(e).type(MediaType.APPLICATION_JSON).build();
	}
}

Finally our RESTful webservice just needs to declare the exception thrown

@Path("/foo")
@Stateless
public class SomeService {
	@GET
	public Response getSth() throws CustomException {
		// do sth.
		return Response.ok().build();
	}
}

Sub-Resources

Sometimes when a web-service implementation is growing too big or we want to encapsulate similar operations in a separate class, we may separate concerns using sub-resources:

This is our base web-service that’s getting the sub-resources injected and delegates the work to its sub-resources.

@Path("/permissions")
@Stateless
public class PermissionService {
	@Inject
	ReadPermissionService readPermissionService;
 
	@Inject
	WritePermissionService writePermissionService;
 
	@Path("read")
	public ReadPermissionService readPermissions(){
		return readPermissionService;
	}
 
	@Path("write")
	public WritePermissionService writePermissions(){
		return writePermissionService;
	}
}

This is one example of a sub-resource here:

@Stateless
public class ReadPermissionService {
	@GET
	@Path("{name}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getReadPermission(@PathParam("name") final String name) {
		// do stuff
		return Response.ok().build();
	}
}

Setting the REST-Service’s Application Path

Say we’d like to set “/rs” as the prefix for all RESTful web-services in our application, depending on the application server and JAX-RS version used we may either set it by modifying the web.xml or using a configuration class.

Option 1: Modify web.xml

<servlet>
	<servlet-name>RESTServlet</servlet-name>
	<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>RESTServletSPI</servlet-name>
	<url-pattern>/rs/*</url-pattern>
</servlet-mapping>

Option 2: Configuration class

@ApplicationPath("/rs")
public class RESTConfiguration extends Application {}

Obtain Contextual Information

Sometimes we need to access information from the HTTP request or the servlet configuration in our REST service.

JAX-RS allows us to inject instance of the following contextual objects using the qualifier @Context.

The following classes may be injected:

An example:

@GET
public Response echo(@Context HttpServletRequest req){
	// do stuff with request ..
	return Response.ok().build();
}

Handling Files with specific Media Types

Let’s say we have an image store and we’d like to allow the users of our service to download images of different media types by their given file-name.

This is our solution:

@GET
@Path("/imagestore/{image}")
@Produces("image/*")
public Response fetchImage(@PathParam("image") final String image) {
	File imageFile = new File("/tmp/" + image);
 
	if (!imageFile.exists()) {
		throw new WebApplicationException(404);
	}
 
	String mediaType = new MimetypesFileTypeMap().getContentType(imageFile);
	return Response.ok(imageFile, mediaType).build();
}

We’re now able to download an image with the correct media type using the following URL: /rs/images/head.png

Handling Generic Collections

Type erasure might cause errors when the type information in a generic collection is needed to select a suitable message body writer. That’s why we’re using the wrapper class, GenericEntity<T> here.

@GET
@Path("/foo")
public Response getUsers(){
	List<User> users = userBean.findAll();
	return Response.ok(new GenericEntity<List<User>>(users){}).build();
}

De-/Serialize Custom Domain Objects

Sometimes we encounter domain objects that may not be wrapped using JAX-B annotations or Jackson/YourTrustedJsonProvider annotations so that we need to write custom serializers and deserializers for them.

Here is an example for this use-case:

Custom MessageBodyWriter

@Provider
public class CarToExoticFormatWriter implements MessageBodyWriter<Car> {
 
  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return Car.class.isAssignableFrom(type);
  }
 
  @Override
  public long getSize(Car car, Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) {
    return -1;
  }
 
  @Override
  public void writeTo(Car car, Class<?> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, Object> headers, OutputStream out) throws IOException {
    Writer Writer = new OutputStreamWriter(out);
	// ..
    writer.flush();
    writer.close();
  }
}

Custom MessageBodyReader

@Provider
@Consumes("mycustomformat")
public class MyCustomUnmarshaller implements MessageBodyReader {
 
	@Override
	public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) {
		return true;
	}
 
	@Override
	public Object readFrom(Class aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap multivaluedMap, InputStream is) throws IOException, WebApplicationException {
		Object result = .... // unmarshal here
 
		return result;
	}
}

Resources

Search
Categories