Wednesday, January 20, 2016

Jersey RESTful web service tutorial. Exceptions handling

When you develop application that provides RESTful web services it is important to handle exceptions that might occur appropriately. Any exception should be processed and client should receive corresponding descriptive response from web service.

Let's look at how to do it.

This post is available in Russian.

We will modify the application created in "Jersey RESTful web service tutorial. The basics" post.

Create ErrorDto with code and message properties to store data about occurring errors.
Add @XmlRootElement annotation to transform ErrorDto objects into JSON format at runtime:
package com.jersey.tutorial.dto;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ErrorDto {

    private int code;
    private String message;

    public ErrorDto() {

    }

    public ErrorDto(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Create class for exception that will be thrown if requested quote is not found:
package com.jersey.tutorial.exception;

public class QuoteNotFoundException extends Exception {

    public QuoteNotFoundException(String message) {
        super(message);
    }
}

To handle exceptions create QuotesExceptionMapper class. It should implement ExceptionMapper interface.
Add @Provider annotation for it to set that it should be used as ExceptionMapper implementation at runtime.

Implement exceptions handling logic in toResponse() method:
  • if QuoteNotFoundException is thrown then server response should have 404 Not Found status.
    Otherwise, response status should be 400 Bad Request
  • status code and error message are stored in ErrorDto object that is returned to web service client in Response object
package com.jersey.tutorial.exception;

import com.jersey.tutorial.dto.ErrorDto;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class QuotesExceptionMapper implements ExceptionMapper<Exception> {

    public Response toResponse(Exception exception) {
        Response.Status status;
        String message;

        if (exception instanceof QuoteNotFoundException) {
            status = Response.Status.NOT_FOUND;
            message = exception.getMessage();
        } else {
            status = Response.Status.BAD_REQUEST;
            message = "Bad request";
        }

        ErrorDto errorDto = new ErrorDto(status.getStatusCode(), message);
        return Response.status(status).entity(errorDto).type(MediaType.APPLICATION_JSON_TYPE).build();
    }
}

Change getQuote(), updateQuote() and deleteQuote() methods of QuotesService class to throw QuoteNotFoundException if quote is not found in HashMap:
package com.jersey.tutorial.service;

import com.jersey.tutorial.dto.QuoteDto;
import com.jersey.tutorial.exception.QuoteNotFoundException;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class QuotesService {

    private static final QuotesService INSTANCE = new QuotesService();
    private static final Map<Integer, QuoteDto> QUOTES = Collections.synchronizedMap(new HashMap<Integer, QuoteDto>());

    private Integer id = 0;

    public static QuotesService getInstance() {
        return INSTANCE;
    }

    public QuoteDto getQuote(Integer id) throws QuoteNotFoundException {
        QuoteDto quoteDto = QUOTES.get(id);

        if (quoteDto == null) {
            throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot retrieve it", id));
        }

        return quoteDto;
    }

    public void addQuote(QuoteDto quote) {
        synchronized(QUOTES) {
            id++;
            QUOTES.put(id, quote);
        }
    }

    public void updateQuote(Integer id, QuoteDto updatedQuoteDto) throws QuoteNotFoundException {
        synchronized(QUOTES) {
            QuoteDto quoteDto = QUOTES.get(id);

            if (quoteDto == null) {
                throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot update it", id));
            } else {
                QUOTES.put(id, updatedQuoteDto);
            }
        }
    }

    public void deleteQuote(Integer id) throws QuoteNotFoundException {
        synchronized(QUOTES) {
            QuoteDto quoteDto = QUOTES.get(id);

            if (quoteDto == null) {
                throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot delete it", id));
            } else {
                QUOTES.remove(id);
            }
        }
    }
}

Change signatures for getQuote(), updateQuote() and deleteQuote() methods of QuotesResource class to support possible QuoteNotFoundException:
package com.jersey.tutorial.ws;

...

@Path("/quotes")
public class QuotesResource {

    ...
    
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response getQuote(@PathParam("id") Integer id) throws QuoteNotFoundException {
        ...        
    }

    ...    

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response updateQuote(@PathParam("id") Integer id, QuoteDto quoteDto) throws QuoteNotFoundException {
        ...        
    }

    @DELETE
    @Path("/{id}")
    public Response deleteQuote(@PathParam("id") Integer id) throws QuoteNotFoundException {
        ...
    }
}

Build the project by executing "mvn clean install" command:
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building jersey-exceptions-tutorial 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ jersey-exceptions-tutorial ---
[INFO] Deleting C:\Blog\jersey-exceptions-tutorial\target
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ jersey-exceptions-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ jersey-exceptions-tutorial ---
[INFO] Compiling 7 source files to C:\Blog\jersey-exceptions-tutorial\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ jersey-exceptions-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Blog\jersey-exceptions-tutorial\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ jersey-exceptions-tutorial ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.19.1:test (default-test) @ jersey-exceptions-tutorial ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-war-plugin:2.6:war (default-war) @ jersey-exceptions-tutorial ---
[INFO] Packaging webapp
[INFO] Assembling webapp [jersey-exceptions-tutorial] in [C:\Blog\jersey-exceptions-tutorial\target\tutorial]
[INFO] Processing war project
[INFO] Webapp assembled in [466 msecs]
[INFO] Building war: C:\Blog\jersey-exceptions-tutorial\target\tutorial.war
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ jersey-exceptions-tutorial ---
[INFO] Installing C:\Blog\jersey-exceptions-tutorial\target\tutorial.war to C:\Users\alex\.m2\repository\jersey-exceptions-tutorial\jersey-exceptions-tutorial\1.0-SNAPSHOT\jersey-exceptions-tutorial-1.0-SNAPSHOT.war
[INFO] Installing C:\Blog\jersey-exceptions-tutorial\pom.xml to C:\Users\alex\.m2\repository\jersey-exceptions-tutorial\jersey-exceptions-tutorial\1.0-SNAPSHOT\jersey-exceptions-tutorial-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.973s
[INFO] Finished at: Tue Jan 19 12:14:19 EET 2016
[INFO] Final Memory: 17M/220M
[INFO] ------------------------------------------------------------------------

Copy created war-file into Tomcat webapps directory, start the server and verify how our application works with REST client, like Postman.

Try to retrieve a non-existing quote:




















Application response has "404 Not Found" status and message that describes the problem.
Try to modify and delete a non-existing quote:





















The same result.
Now try to make application throw another exception (not QuoteNotFoundException) by using incorrect URI:




















Application sent a response with "400 Bad Request" status as expected.

Project structure:































Application source code is available at https://subversion.assembla.com/svn/jersey-exceptions-tutorial/trunk/.

Recommended posts:

No comments:

Post a Comment