Handling Exceptions In Spring MVC – Part 2

In ‘Handling Exceptions In Spring MVC – Part 1′ we talked about how the servlet container can be set up to manage exceptions and status codes, as well as some of the issues you can face by having the servlet container be responsible for this. We also went over the HandlerExceptionResolver interface in Spring, including the SimpleMappingExceptionResolver implementation which is an excellent base for extension.

In this post I am going to show a possible extension to the SimpleMappingExceptionResolver – one which allows us to define exception priorities as well as sending different notifications based on the priorities. Included at the bottom of this post is a link to a sample web app which shows the use of the SimpleMappingExceptionResolver and the newly created one shown in the post. The web app uses both the 2.0 and 2.5 styled controllers and is only meant as a small example.

I mentioned in the previous post that this would be a ‘two post’ post. Well, after sitting down and planning out what I would like to cover, it seems it might be three posts, sorry. Also I would be very interested in other people’s views on this topic, as I believe there is no ‘right’ answer, but instead a different set of answers for a different set of situations.

Quick Refresh

The SimpleMappingExceptionResolver is a perfectly good implementation of the HandlerExceptionResolver interface. Its basic configuration is like so :

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
  <map>
    <entry key="DataAccessException" value="data-error" />
    <entry key="com.stuff.MyAppRuntimeException" value="app-unchecked-error" />
    <entry key="com.stuff.MyAppCheckedException" value="app-checked-error" />
  </map>
</property>
<property name="defaultErrorView" value="general-error"/>
</bean>

The above example shows three features of the SimpleMappingExceptionResolver (SMER) :

  1. default error page – All exceptions, checked and unchecked, will be resolved to either a specific page individual to them, or to a generic page which is set by the defaultErrorView.
  2. specific exceptions - As per the above example, “app-unchecked-error” and “app-checked-error” are the views to “com.stuff.MyAppCheckedException” and “com.stuff.MyAppRuntimeException” respectively.
  3. substring and subclass support – DataAccessException will match to org.springframework.dao.DataAccessException and com.myapp.DataAccessException, including any subclasses of either.

Have you read the SMER api docs? If you haven’t, READ IT!!!, it’s a wealth of information. In fact, if you really want to learn, have a look at the source code, download it from the Spring website and nosey through it, it’s the only true way to learn the api.

Extending SimpleMappingExceptionResolver – Stage One

Okay, enough about the SMER, let’s get onto extending. For our new HandlerExceptionResolver we should define some simple and clear requirements:

  1. When an Exception is handled by the resolver, a notification might be sent out
  2. Several different Notification services can be set up, each with a priority (user definable)
  3. Exceptions can be assigned a priority as to determine which Notification service to use
  4. If no priority is defined for an Exception then no notification is to be sent
  5. All exceptions handled should be logged with the priority assigned to the Exception type

Is that enough? Well, for this example it is. I am going to only show a possible way of extending SMER; not the perfect implementation, just a possible one.

For this we are going to need a NoficationService interface:

public interface NotificationService {
  public void sendNotification(String message, Exception exception);
}

And a simple implementation which will send an email:

import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class EmailNotificationEmail implements NotificationService {
  private MailSender mailSender;
  private SimpleMailMessage templateMessage;

  public static final Log log = LogFactory.getLog(EmailNotificationService.class);

  public EmailNotificationService(MailSender mailSender, SimpleMailMessage templateMessage){
    this.mailSender = mailSender;
    this.templateMessage = templateMessage;
  }

  public void sendNotification(String message, Exception exception) {

    SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
    msg.setText("Application Message - " + message + " : " + exception.getMessage());

    try{
      this.mailSender.send(msg);
    } catch(MailException ex) {
      // simply log it and go on...
      log.fatal("Email Notification message could not sent", ex);
    }
  }
}

(have a read here about the email apis provided by Spring)

And this is to go in the applicationContext-services.xml

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.mycompany.com"/>
</bean>

<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="to" value="techsupport@mycompany.com"/>
<property name="from" value="my-app@mycompany.com"/>
<property name="subject" value="My-App has had a fatal exception occur"/>
</bean>

<bean id="emailNotificationService" class="com.example.myapp.EmailNotificationService">
  <constructor-arg><ref bean="mailSender"></constructor-arg>
  <constructor-arg><ref bean="templateMessage"/></constructor-arg>
</bean>

So far we have a NotificationService interface, EmailNotificationService implementation, and an application context to wire it all up. So now we need to create the SMER extension.

Extending SimpleMappingExceptionResolver – Stage Two

Below is the code for the NotifyingExceptionResolver. Please remember that formatting of the code isn’t great, but all code is available for download at the end of the post.

public class NotifyingExceptionResolver extends  SimpleMappingExceptionResolver {
  private Properties priorityExpMappings;
  private Map priorityNSMappings;
  private String notificationMessage = null;

  private Log log = LogFactory.getLog(NotifyingExceptionResolver.class);

  public void setPriorityExceptionMappings(Properties  priorityExpMappings){
    this.priorityExpMappings = priorityExpMappings;
  }

  public void setPriorityNotificationServicesMappings (Map priorityNSMappings){
    this.priorityNSMappings = priorityNSMappings;
  }

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

  @Override
  protected ModelAndView doResolveException(HttpServletRequest request,  HttpServletResponse response, Object handler, Exception ex) {
    log.warn("An Exception has occured in the application", ex);
    sendNotification(ex);
    return super.doResolveException(request, response, handler, ex);
  }

  private void sendNotification(Exception ex) {
    String priority = resolvePriority(this.priorityExpMappings, ex);
    NotificationService notificationService = resolveNotificationService (this.priorityNSMappings, priority);
    String message = (notificationMessage == null ? priority :  notificationMessage);

    if(notificationService != null) {
      log.debug("notification message was sent");
      notificationService.sendNotification(message, ex);
    }
  }

  private String resolvePriority(Properties priorityExpMappings,  Exception ex){
    return this.findMatchingViewName(priorityExpMappings, ex);
  }

  private NotificationService resolveNotificationService (Map priorityNSMappings, String priority){
    NotificationService notificationService = null;
    notificationService = priorityNSMappings.get(priority);
    if (notificationService != null && logger.isDebugEnabled()) {
      logger.debug("Resolving to a notification service for priority ["  + priority + "]");
    }
    return notificationService;
  }
}

You will notice two important variables – priorityExpMappings and priorityNSMappings. These variables hold the mappings between the exception types, priorities and notification services. You will also notice the method I have used as my hook in, doResolveException. As this method is where the action is, all I have done is added some more behavior and then called the super method. I have cheated a bit in the resolvePriority method by using the smarts behind findMatchingViewName to determine the priority linked to the exception. From there on in it’s pretty straightforward; I determine the priority based on the exception, find the notification service, and send a message.

Here is the application context definition which goes with it :

<bean class="com.mycompany.myapp.web.exceptionresolvers.NotifyingExceptionResolver">
<property name="exceptionMappings">
    <map>
      <entry key="SpecialCheckedApplicationError" value="errors/checked-error"/>
      <entry key="SpecialRuntimeApplicationError" value="errors/runtime-error"/>
    </map>
  </property>
<property name="priorityExceptionMappings">
    <map>
      <entry key="SpecialRuntimeApplicationError" value="High" />
      <entry key="SpecialCheckedApplicationError" value="High" />
    </map>
  </property>
<property name="priorityNotificationServicesMappings">
    <map>
      <entry key="High" value-ref="emailNotificationService" />
    </map>
  </property>
<property name="defaultErrorView" value="errors/general-error" />
</bean>

You will notice that I still define the exceptionMappings property and the defaultErrorView property. There is no doubt I have extended from the SMER; I have just added some cherries to an already creamed pie.

Would this be the best option all the time?

Is extending the SimpleMappingExceptionResolver best for all cases? No, nothing is best for all cases, but it is best for most. In fact, just using the SMER and not extending it is best for most, and extending it is only for required cases. To be honest, creating a notification system like I have is quite contrived as the logging system can use an smtp gateway to do what I have done, and creating new logging appenders isn’t hard either.

I have a lot more to cover and talk about, but this post is already 1000+ words. The final post in the series will expand on the question of what errors should be captured where; be it in the web.xml, controller, or handler exception resolver, checked and runtime.

This post merely shows that extending and customizing the SMER is not hard and very feasible, but just make sure you know why you are doing it.

Sample Application

To download the sample web app with email notification service, click here. This web app is built using Maven and thus requires a bit of knowledge of Maven to package up. If you’re not a Maven fan then it shouldn’t be that hard to refactor into a single package and source folder.

(Please note, make sure you download Spring MVC 2.5.2, 2.5.1 has issues with annotated controllers throwing checked exceptions)

References

Reporting Application Errors by Email (using logging)

About these ads

13 Comments

Filed under Spring MVC

13 responses to “Handling Exceptions In Spring MVC – Part 2

  1. dorzel

    Nice article.
    But the link for download doesn’t work.
    http://files.curlybrackets.co.nz/ gives a 404 Error.
    And this 404 isn’t a pretty html page ;)

  2. Josh Kalderimis

    Hi dorzel,

    Thanks for letting me know, it seems I have a small domain name issue to sort out. I will get the site up and running asap.

    Until then, you can access the file at http://files.curlybrackets.co.nz.s3.amazonaws.com/Exception%20Handling%20-%20Part%202.zip

    Josh

  3. Deep Kagda

    Hey,

    That was really a gr8 help for Spring Exceptions.

    Thanks a ton.
    Deep.

  4. TAW

    Thanks for this article – very useful.

    -TAW

  5. Spongiform says : I absolutely agree with this !

  6. Thusjanthan Kubendranathan

    Hi,

    Will this intercept HTTP 500 errors as well? I have the exact same setup but it throws a 500 exception:

    HTTP Status 500 -

    type Exception report

    message

    description The server encountered an internal error () that prevented it from fulfilling this request.

    exception

    org.springframework.orm.hibernate3.HibernateSystemException: Provided id of the wrong type. Expected: class java.lang.Integer, got class java.lang.String; nested exception is org.hibernate.TypeMismatchException: Provided id of the wrong type. Expected: class java.lang.Integer, got class java.lang.String
    org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:659)

    For this should I have the setup in web.xml or will this work fine?

  7. Bas

    Maybe you could mention the use of the warnLogCategory property when applying SimpleMappingExceptionResolver in Spring.

    Without it there is no logging of the Exception in Spring:

  8. danichelios

    Excelent article, much better than spring reference and some books

  9. Richard Allen

    Thanks for the info. There is an error you might want to correct. You have this:

    public class EmailNotificationEmail …
    public EmailNotificationService(…) …

    Notice the class is named “EmailNotificationEmail” and the constructor is named “EmailNotificationService”.

  10. ruwan

    Can we add Spring exception handlers to override default 500 error page with this method? If not how can we do it with Spring with or without SimpleMappingExceptionResolver?

  11. I’m starting a new project with spring 3 using annotations for everything, including hibernate, and MVC. I really love spring now. The xml approach made the spring learning curve much steeper before.

  12. Andrew

    Great post. Thank you in understanding Spring’s central exception handling. You’ve solve a lot of my problems.

  13. Very nicely (newby friendly) written !! Thank you !!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s