In this blog post I am going to share with you how to handle Exceptions in your RESTFul Web Service API and how to return expected JSON Payload message back to a mobile application if an error takes place in your Java code. For that I am going to use the exception @Provider annotation and the ExceptionMapper class to handle Checked and Unchecked Exceptions in our RESTFul Web Services API.
And the reason why I like using the ExceptionMapper approach is because it is much more either to handle error messages in my Web Services Application this way. I can throw exception messages whenever I need and I do not have to worry about catching them and simply reply on the ExceptionMapper class that the exception message will be handled and a proper message JSON Payload I have defied will be returned back to a mobile application. It is very convenient and it is very easy.
In one of my previous blog posts I have shared with you an example of “How to Create a RESTful Web Service to Save a New User in a Database”. So, lets assume that when our Web Service receives a request to create a new user, an error takes place and we need to return back to a mobile application a JSON payload with an error message, which our mobile application can read, understand and display a proper friendly message to a user. So here is what I am going to do in my Web Service service layer class:
I am going to check if user with provided user name already exits and if it does exist, I am going to throw a specific custom Exception message “CreateRecordException”. This java exception message will then be handled by an ExceptionMapped class and will return back to a mobile application a JSON message of the following format:
{ "errorMessageKey": "COULD_NOT_CREATE_USER_PROFILE", "errorMessageValue": "Record already exists" }
Where:
- the errorMessageKey can be used by mobile application to pick a friendly error message description from it’s Internationalized property file. For example if app interface is in English then a message will be displayed in English language and if app interface is in French, an error message will be displayed in French.
- And the errorMessageValue is the description mostly purposed for a mobile app developer to understand why this particular error message was triggered.
The above mentioned JSON message structure does not have to be exactly as it is defined above. It is my approach. You can add other useful details to this error message like HTTP Status code for example, so that the developer of mobile app does not have to inspect HTTP headers looking for a HTTP status code and you can also add to this JSON message a URL to an online documentation, so that the app developer can look up a more detailed description of this error message key and learn about its reasons and what to do to resolve this situation. So feel free to expand the above JSON Payload with additional information you might want to provide your Web Service API clients with.
For me to be able to return back an error message of the above mentioned format in JSON, I will need to create an ErrorMessage java class like the one below. Every instance field if this class I am going to create will be transformed into a JSON message key and the value that the class instance field holds, will become a value that the JSON message key holds. So once again, if you feel you would like to add more details to your error JSON message payload, then this java class is the place to do it:
Create an ErrorMessage Java Class
package com.appsdeveloperblog.ws.ui.rest; import javax.xml.bind.annotation.XmlRootElement; /** * * @author skargopolov */ @XmlRootElement public class ErrorMessage { private String errorMessageValue; private String errorMessageKey; public ErrorMessage() {} public ErrorMessage(String errorMessageValue, String errorMessageKey) { this.errorMessageValue = errorMessageValue; this.errorMessageKey = errorMessageKey; } /** * @return the errorMessageValue */ public String getErrorMessageValue() { return errorMessageValue; } /** * @param errorMessageValue the errorMessageValue to set */ public void setErrorMessageValue(String errorMessageValue) { this.errorMessageValue = errorMessageValue; } /** * @return the errorMessageKey */ public String getErrorMessageKey() { return errorMessageKey; } /** * @param errorMessageKey the errorMessageKey to set */ public void setErrorMessageKey(String errorMessageKey) { this.errorMessageKey = errorMessageKey; } }
Please note the use of @XmlRootElement annotation at the top of this class. It is an object of this class that will be transformed into XML or JSON representation and returned back as part of the Response to a calling mobile application.
Create Custom Exception Java Class
To handle a specific situation in your Web Service API like “NO RECORD FOUND” or “MISSING REQUIRED FIELD” or “RECORD EXISTS” we can create a specific Exception class like the one below:
package com.appsdeveloperblog.ws.shared.exceptions; /** * * @author skargopolov */ public class CreateRecordException extends RuntimeException { public CreateRecordException(String message) { super(message); } }
and to handle this exception message when it takes place and to map it to an ErrorMessage java class we have created above, we will need to make our custom Exception class implement the ExceptionMapper interface. Have a look at the example below how to do it. Please have a look at the overriden method toResponse which instantiates an ErrorMessage object, populates it with needed details and then sets it as an Entity to a Response object which will be returned back to our mobile application.
Create Exception Mapper and Implement ExceptionMapper Interface
package com.appsdeveloperblog.ws.shared.exceptions; import com.appsdeveloperblog.ws.ui.rest.ErrorMessage; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * * @author skargopolov */ @Provider public class CreateRecordExceptionMapper implements ExceptionMapper<CreateRecordException> { @Override public Response toResponse(CreateRecordException exception) { ErrorMessage errorMessage = new ErrorMessage( exception.getMessage(), ErrorMessages.COULD_NOT_CREATE_USER_PROFILE.name() ); return Response.status(Response.Status.BAD_REQUEST). entity(errorMessage). build(); } }
Also, please note that that as a second parameter to my ErrorMessage object I passed a ErrorMessages.COULD_NOT_CREATE_USER_PROFILE enum. I could hard code the String value of the message I want to return back but I like to have all the error message String values organized in one place. This is why I have created a new enum class and have organized all the error message strings in that enum. The enum with error messages is below.
package com.appsdeveloperblog.ws.shared.exceptions; /** * * @author skargopolov */ public enum ErrorMessages { MISSING_REQUIRED_FIELD("Missing required field. Please check documentation for required fields"), COULD_NOT_CREATE_USER_PROFILE("Could not create user profile"), COULD_NOT_UPDATE_USER_PROFILE("Could not update user profile"), COULD_NOT_DELETE_USER_PROFILE("Could not delete user profile"), NO_RECORD_FOUND("No record found for provided id"), RECORD_ALREADY_EXISTS("Record already exists"), INTERNAL_SERVER_ERROR("Something went wrong. Please repeat this operation later."); private String errorMessage; ErrorMessages(String errorMessage) { this.errorMessage = errorMessage; } /** * @return the errorMessage */ public String getErrorMessage() { return errorMessage; } /** * @param errorMessage the errorMessage to set */ public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } }
Ok. So now we are ready to make use of our custom exception class and throw an exception message when we need it and then let the ExceptionMapper catch that exception message and return back to our mobile application a proper JSON payload which it can read and understand. And the best part of it is that I do not have to worry about catching this Exception message. The framework will do it for me. An ExceptionMapper associated with this Exception message will be triggered and my error message will be transformed from a Java class to a proper JSON or XML message.
Ok here is the Web Service Entry Point.
But before you continue further I would like to mention that I have recorded a series of video lessons on how to create and handle exceptions in RESTful Web Services and have included them into my video course: REST API with Java JAX-RS. Create and Deploy to Amazon Cloud.
The Users Web Service Entry Point
Here is an example of my /users web service entry point that accepts HTTP POST Request and attempts to create a new user profile.
package com.appsdeveloperblog.ws.entrypoints; import com.appsdeveloperblog.ws.service.UsersService; import com.appsdeveloperblog.ws.shared.dto.UserProfileDto; import com.appsdeveloperblog.ws.ui.rest.UserProfile; import com.appsdeveloperblog.ws.ui.rest.UserProfileResponse; import com.appsdeveloperblog.ws.utils.Secured; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * * @author skargopolov */ @Component @Path("/users") public class Users { @Autowired UsersService usersService; @POST @Consumes(MediaType.APPLICATION_JSON) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) public UserProfileResponse createUserProfile(UserProfile userProfile) { UserProfileResponse returnValue = null; UserProfileDto userProfileDto = new UserProfileDto(); BeanUtils.copyProperties(userProfile, userProfileDto); UserProfileDto storedUserDetails = usersService.createUser(userProfileDto); if (storedUserDetails != null && !storedUserDetails.getFirstName().isEmpty()) { returnValue = new UserProfileResponse(); BeanUtils.copyProperties(storedUserDetails, returnValue); } // And when we are done, we can return user profile back return returnValue; } }
The Service Layer Class
My /users web service entry point delegates the below tasks to a UsersService class:
- Check if user exists
- Create User Profile
package com.appsdeveloperblog.ws.service; import com.appsdeveloperblog.ws.shared.dto.UserProfileDto; /** * * @author skargopolov */ public interface UsersService { public UserProfileDto createUser(UserProfileDto user); public UserProfileDto getUserProfileByUserName(String userName); }
package com.appsdeveloperblog.ws.service.impl; import com.appsdeveloperblog.ws.io.dao.Database; import com.appsdeveloperblog.ws.io.entity.UserProfileEntity; import com.appsdeveloperblog.ws.service.UsersService; import com.appsdeveloperblog.ws.shared.dto.UserProfileDto; import com.appsdeveloperblog.ws.shared.exceptions.CreateRecordException; import com.appsdeveloperblog.ws.shared.exceptions.ErrorMessages; import com.appsdeveloperblog.ws.shared.exceptions.UserServiceException; import com.appsdeveloperblog.ws.utils.AuthenticationUtil; import java.security.spec.InvalidKeySpecException; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("usersService") public class UsersServiceImpl implements UsersService { Database database; AuthenticationUtil authenticationUtil; @Autowired public UsersServiceImpl(Database database, AuthenticationUtil authenticationUtil) { this.database = database; this.authenticationUtil = authenticationUtil; } @Override public UserProfileDto createUser(UserProfileDto userDto) throws UserServiceException, CreateRecordException { UserProfileDto returnValue = null; // Check if user exist UserProfileDto existingUser = this.getUserProfileByUserName(userDto.getUserName()); if(existingUser != null) { throw new CreateRecordException(ErrorMessages.RECORD_ALREADY_EXISTS.getErrorMessage()); } // Generate salt String salt = authenticationUtil.generateSalt(30); // Generate secure user password String secureUserPassword = null; try { secureUserPassword = authenticationUtil. generateSecurePassword(userDto.getUserPassword(), salt); } catch (InvalidKeySpecException ex) { Logger.getLogger(UsersServiceImpl.class.getName()).log(Level.SEVERE, null, ex); throw new UserServiceException(ex.getLocalizedMessage()); } //Generate secure public user id String securePublicUserId = authenticationUtil.generateUserId(30); userDto.setSalt(salt); userDto.setUserPassword(secureUserPassword); userDto.setUserId(securePublicUserId); UserProfileEntity userEntity = new UserProfileEntity(); BeanUtils.copyProperties(userDto, userEntity); // Connect to database try { this.database.openConnection(); UserProfileEntity storedUserEntity = this.database.saveUserProfile(userEntity); if (storedUserEntity != null && storedUserEntity.getId() > 0) { returnValue = new UserProfileDto(); BeanUtils.copyProperties(storedUserEntity, returnValue); } } finally { this.database.closeConnection(); } return returnValue; } @Override public UserProfileDto getUserProfileByUserName(String userName) { UserProfileDto returnValue = null; if( userName == null || userName.isEmpty() ) return null; // Connect to database try { this.database.openConnection(); UserProfileEntity userProfileEntity = this.database.getUserProfileByUserName(userName); if (userProfileEntity != null && !userProfileEntity.getUserName().isEmpty()) { returnValue = new UserProfileDto(); BeanUtils.copyProperties(userProfileEntity, returnValue); } } finally { this.database.closeConnection(); } return returnValue; } }
Please note that inside of my createUser method, I check if user already exists, and if it does I throw a CreateRecordException Exception message. Please also not that I do not try to catch that exception message either in this or in my web service entry point class. I simply throw a custom exception message which is mapped to an ExceptionMapper class and then I rely on the ExceptionMapper class above to catch this exception message. Which it does very well. The CreateRecordException I throw in the UsersServiceImpl class, will be handled my CreateRecordExceptionMapper and a proper error message JSON Payload will be returned back to our mobile application.
Handle Unchecked Exception Messages in RESTful Web Service
If you want to catch all other unhandled exception messages and also map them to an ErrorMessage java bean we have created and return back a proper JSON Payload message rather than an very long Exception stracktrace, then you can create a Generic ExceptionMapper class like the one below:
@Provider public class GenericExceptionMapper implements ExceptionMapper<Throwable> { public Response toResponse(Throwable exception) { ErrorMessage errorMessage = new ErrorMessage( exception.getMessage(), ErrorMessages.INTERNAL_SERVER_ERROR.name() ); return Response.status(Response.Status.INTERNAL_SERVER_ERROR). entity(errorMessage). build(); } }
Note that this GenericExceptionMapper class implements the ExceptionMapper<Throwable> rather than ExceptionMapper<CreateRecordException> and please also not that the toResponse method accepts as an argument the Throwable in this case rather than a specific custom exception message we have defined.
Give it a try! I think you are going to like using the ExceptionMapper interface to handle exception messages in your RESTful Web Services API.
Don’t forget that you can also watch all of the above mentioned on video in my video course: REST API with Java JAX-RS. Create and Deploy to Amazon Cloud.
Happy coding!