Creating Custom Exception Classes in Java


Introduction

My first piece of advice: Search the web. There is a ton of information that is available to you on the Internet. However, beware where you go for information. There are many places with bad information as well. For this topic, the safest source of information is the Oracle The Java Exceptions Tutorials.

Starting with the basics, the first thing to do is determine whether or not you need the custom exception class to begin with. We are going to assume we need to handle an exception that it is not clearly represented already in Java.

To create a custom exception class, we must use a name that it is meaningful. Typically, the exception class is named after a process or a class name that will throw the exception. For instance, if I create a MessageReceiver class that could result in an exception, I might consider naming my exception class MessageReceiverException. The UML diagram below depicts this relationship between the class and the custom exception class:


Notice that there is only an association relationship between MessageReceiver and MessageReceiverException. That means that MessageReceiver “uses” MessageReceiverException.

Choosing a Name for the Class

In my example, I chose for my custom exception class to extend Exception. This does not have to be the case all the time. Any Exception subclass can be used as the parent class of MessageReceiverException. However, a quick perusal of those subclasses shows that they are inappropriate because they are either too specialized or completely unrelated to MessageReceiverException. Therefore, the parent class of MessageReceiverException should be Exception.

Exception Class Contents: Constructors

Your class should contain 4 constructors. Following the UML diagram above, my MessageReceiverException class should look as follows:


public class MessageReceiverException extends Exception
{
 private String message;

 public MessageReceiverException()
 {
  super();
  message = "MessageReceiver class encountered an unknown condition.";
 }
 
 public MessageReceiverException(String message)
 {
  super(message);
  this.message = message;
 }
 
 public MessageReceiverException(String message, Throwable cause)
 {
  super(message, cause);
  this.message = message;
 }
 
 public MessageReceiverException(Throwable cause)
 {
  super(cause);
  message = (cause == null ? null : cause.toString());
 }
}


Examining the code, you can see how the no-argument constructor could be used for a default case where a default message is constructed. In the next two cases, a message is constructed by the user (client) of the class; in this example, MessageReceiver. Because the MessageReceiver has two methods, it is possible that both could result in an exception being thrown. However, the message generated for exception occurring inside the onMessage method might (and most likely will) be different than the message generated inside the processMessage method. The last case examines the cause (Throwable) object and obtains the message set in it if the object is not null.

Getting the Message

The Throwable class (the immediate parent of Exception) implemented many methods that the Exception class inherited and uses as-is (no method overriding). Because of this fact, it should not be necessary to override these methods. Amongst the most used methods are: getLocalizedMessage, getMessage, printStackTrace, and toString.

The Client Class

Creating a custom exception class is meaningless unless there is another class who will throw that type of exception when something goes wrong. Therefore, the client class’ affected methods must indicate the kind of exceptions they throw (if any). In this example, the class has two methods that could result in the same exception TYPE (not the same exception) being thrown. However, this does not have to be the case all the time. Additionally, a method could throw more than one type of exception. On such cases, the exceptions must be listed separated by comma (i.e. throws IOException, FileNotFoundException, etc.)


public interface IMessageReceiver
{
 public void onMessage(Message message) throws MessageReceiverException;
}
public class MessageReceiver implements IMessageReceiver
{
 @Override
 public void onMessage(Message message) throws MessageReceiverException
 {
  if(message != null)
  {
   processMessage(message);
  }
  else
  {
   throw new MessageReceiverException("The message object was null.");
  }
 }
 
 private void processMessage(Message message) throws MessageReceiverException
 {
  if(message.getContents() != null && !message.getContents().isEmpty())
  {
   //TODO: Process the contents of the message and do something with it
  }
  else
  {
   throw new MessageReceiverException("The message contains no information.");
  }
 }
}



public class Message
{
 private String to;
 private String from = "";
 private String subject = "";
 private String contents;
 
 public Message(String to, String contents)
 {
  this.to= to;
  this.contents = contents;
 }

 public String getTo()
 {
  return to;
 }
 public String getFrom()
 {
  return from;
 }
 public String getSubject()
 {
  return subject;
 }
 public String getContents()
 {
  return contents;
 }
 public void setFrom(String from)
 {
  this.from = from;
 }
 public void setSubject(String subject)
 {
  this.subject = subject;
 }
 
 @Override
 public String toString()
 {
  StringBuilder strBldr = new StringBuilder();
  strBldr.append("Message/nTo: ");
  strBldr.append(to);
  strBldr.append("\nFrom: ");
  strBldr.append(from);
  strBldr.append("\nSubject: ");
  strBldr.append(subject);
  strBldr.append("\n----------------------------------");
  strBldr.append(contents);
  
  return strBldr.toString();
 }
 
 @Override
 public int hashCode()
 {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((contents == null) ? 0 : contents.hashCode());
  result = prime * result + ((from == null) ? 0 : from.hashCode());
  result = prime * result
    + ((subject == null) ? 0 : subject.hashCode());
  result = prime * result + ((to == null) ? 0 : to.hashCode());
  return result;
 }
 
 @Override
 public boolean equals(Object obj)
 {
  if (this == obj)
   return true;

  if (obj == null)
   return false;

  if (getClass() != obj.getClass())
   return false;

  Message other = (Message) obj;
  if (contents == null)
  {
   if (other.contents != null)
    return false;
  }
  else if (!contents.equals(other.contents))
   return false;
  
  if (from == null)
  {
   if (other.from != null)
    return false;
  }
  else if (!from.equals(other.from))
   return false;
  
  if (subject == null)
  {
   if (other.subject != null)
    return false;
  }
  else if (!subject.equals(other.subject))
   return false;
  
  if (to == null)
  {
   if (other.to != null)
    return false;
  }
  else if (!to.equals(other.to))
   return false;
  
  return true;
 }
}

Comments

Popular posts from this blog

Combining State and Singleton Patterns to Create a State-Machine

Exception Handling: File CRUD Operations Example

The Beauty of the Null Object Pattern