Error Causes, Types and Reactions
Jakob Jenkov |
An application is often limited in what ways it can react to an error. The reason for this is, that an application is not very good at determining what actions are needed to correct an error.
For instance, if a configuration file is missing, you as a developer may know exactly what to do, but the application doesn't. The application only knows what you can program it to know, and this is often more limited than we initially think.
Common Error Reactions
An application typically only has these possible reactions:
- Abort action, and notify the user. Maybe log the error.
- Retry the action at a later time.
- Retry the action against a different service (e.g. backup database, or web service etc.)
Some applications may have further options that are specific to their internal design, or the architecture in which they are deployed. But the above options are the most common possible reactions to errors.
Error Types
Which of the above error reactions should be applied depends on what type of error that occurred. There are typically three kinds of errors that can occur, each of which calls for a different reaction. These three kinds of errors are:
- Client Errors
- Service Errors
- Internal Errors
The diagram below illustrates these error types, and where they occur.
Illustration of the various error types. |
Client Errors
Client errors are errors caused by the clients wrong use of the application. For instance, a user types in the wrong file name, or types in characters where the input should have been a number. Or the client sends an invalid web service request. In other words, the client is not complying with the contract for interaction with the application.
A client error is normal. It is expected to happen from time to time. A client error is not a sign that your application has bugs. It is a sign that your application correctly rejected invalid data or usage.
Client Error Reactions
The most common reaction to a client error is to:
- Abort the request action.
- Close any opened resources (connections, files, streams etc.)
- Free any allocated resources (memory buffers etc.).
- Notify the user of the error.
- For debug or customer service purposes you could also log the error. For instance,
your application might actually classify the error incorrectly as a
client error. Or, the user may contact you to ask why the application rejected
his request. In that case it may be nice to have the error logged.
There is often no reason to raise an alarm to the system operators, though.
Service Errors
Service errors arise if an external service used by the application malfunctions. For instance, the database is down and is thus not responding.
A service error is not a sign that your application has bugs either. Your application is functioning correctly.
You should try to develop your application so that it can survive such temporary external service failures. For instance, if your application uses a connection pool to a database which fails, the application should be able to re-initialize the connection pool, once the database is working correctly again.
Service Error Reactions
The most common reaction to a service error is to:
- Either
- Abort the requested action.
- Retry at a later time, or at a different service instance (e. g. a backup server).
- Close any opened resources (connections, files, streams etc.).
- Free any allocated resources (memory buffers etc.).
- Notify the user of the error.
- Log the error and raise an alarm to the application operators, so they can investigate and correct / restart the malfunctioning service.
Internal Errors
Internal errors are errors that are caused by your application. The cause for such errors could for instance be a bug in the application or invalid configuration. Generally, any error that cannot be determined to be a client error or service error, should be categorized as an internal error.
An internal error is the most severe of the three error types. If an internal error is detected the application is not functioning correctly, and the system will not correct itself automatically. It is necessary to call in the developers to investigate the error further, and maybe deliver a patch to the application.
Internal Error Reactions
The most common reaction to an internal error is to:
- Abort the requested action.
- Close any opened resources (connections, files, streams etc.).
- Free any allocated resources (memory buffers etc.).
- Notify the user of the error.
- Log the error and raise an alarm to the application operators.
- Have developers investigate the error, and possibly deliver a bug fix.
Temporary Internal Errors
An application may have temporary internal errors. For instance, if a web application receives too many requests during a peak usage time, the web server may temporarily be unable to respond. This will, however, fix itself once the load decreases.
Such temporary internal errors are an exception to the statement, that the application will not correct itself. Temporary errors may go away by themselves.
Temporary Internal Error Reactions
The reaction to a temporary internal errors is similar to a service error. The application is working correctly from a functional perspective, but is temporarily unavailable.
Error Classifications by the Application
When an error occurs in your application it should do its best to categorize the error as either a client error, service error, an internal error or a temporary internal error. This is not always easy, but that does not mean the application should not try. I'll try to describe how to do this here.
Any unit in your application may experience the types of errors described earlier. A unit here meaning �a piece of code�, e. g. a larger component, a class, an object, or a method / function.
This diagram shows three units working together:
Three arbitrary, collaborating units of an application. |
These units could be three objects, or three larger grained components, or even three separate systems communicating via remoting or web services or something similar. The granularity of the units is not really important. It is the roles of the units that matter.
It is the middle unit, the executing unit (the component), we are interested in. Actually, almost any unit in your application assumes the roles as both client, executing component, and service. Most units do no function in isolation. They are called by some other unit, and they often also call other units themselves. The role of any given unit just depends on what component you choose to zoom in on as the executing unit.
If a client component does not obey the contract of the executing component, this is a client error. For instance, if the client component calls the executing component with invalid input parameters or while the executing component is in an invalid state.
If the executing component calls a service component, and this service component throws an exception (or returns invalid data etc.), this is a service error.
Any other error which cannot be classified as a client or service error, is an internal error.
This may still seem a bit abstract, so let's look at a concrete example. The diagram below shows the call sequence of a simple web application that uses a database.
The call sequence of a simple web application that uses a database. |
The user types in some data in a form in the browser, and clicks the submit button. The form data is then sent to the web application, which passes it on to the relevant web action.
The web action (JSP, Servlet, Struts Action etc.) receives the request and validates the request parameters. In case of invalid request parameters, the rest of the requested action is aborted, and the user is notified. A client error is logged, if the error is logged at all.
If the request parameters are valid, the web action ends up calling the DAO to insert / update data in the database. The DAO validates the input parameters passed to it from the web action. In case of invalid input parameters, the DAO raises a client exception. From the DAO perspective the client has broken its contract - hence the client exception. When the exception is propagated up to the web action, it is up to the web action to determine what type it perceives the error as. This is known as error re-classification.
Error Re-classification
As an exception is propagated up the call stack, each component propagating the exception may re-classify the exception's error type. It is not always easy to either classify or reclassify errors, but here are two examples describing situations where it is possible.
Example 1
For instance, imagine that the web action passed a user id to the DAO, which does not exist in the database. The DAO checks if the user exists, before carrying out the action. Since the user id is not found in the database, the DAO raises a client exception. The client of the DAO has broken the contract of the DAO.
When presented with an "invalid user id" exception, the web action should know whether the user id came from inside the application (e.g. the session object), or from the client (e.g. a cookie in the browser, or a JavaScript variable in an AJAX application). Thus, the web action may change the error type set the by the DAO, to e.g. internal error.
If, on the other hand, the user id was taken from the request parameters (the query string), the web action may still classify the error as a client error. The request string might have been manipulated by the user.
Example 2
Imagine again a call from the Web Action to the DAO.
If the DAO input parameters are valid, the DAO calls the database driver to get the update through to the database. Let's imagine that the SQL passed to the driver is invalid. The database driver then returns a client error. The database driver contract has been broken by the DAO and from the database driver perspective this is a client error.
From the DAO perspective this may not be a client error, though. If the SQL was generated / injected / hard coded injected into the DAO, and the SQL is invalid, this is an internal error from the DAO perspective.
An exception to this classification would be an application where the user can type in SQL in the user interface. If the DAO knows that the invalid SQL came from the user, then the DAO should keep the classification as a client error. It was an error in the input data given to the DAO.
The web actions also knows that the SQL came from the browser, and thus keeps the error classification as a client error. The application can then react accordingly.
If on the other hand, the database connection is broken, the database driver will raise a service exception. From the database driver's perspective this is a service error. The DAO receives the service error, and keeps that classification when propagating the exception up. So does the web action. The application then notifies the user, and logs the exception.
Error Re-classification Table
Here is a more formal listing of what error types that can be reclassified on their way up the call stack. This table is not the final truth. Your application may contain different reclassifications.
Error Type | Possible Re-classifications |
Client Error |
Stays a client error, if the component receiving the exception knows that the breach of contract was
caused by it's own client. For instance, if the DAO receives a client error signaling invalid
SQL, and the DAO knows that the SQL was typed in by the user of the application,
then the DAO should keep the client error classification.
Is reclassified as an internal error, if the component receiving the error knows that the service contract was broken because of a buggy implementation or configuration of the component calling the service. For instance, if the DAO generates SQL and the database driver raises an �invalid SQL exception� which is a client error. The DAO should then reclassify this error as an internal error. Client errors are almost never reclassified as service errors. If the service tells a component that it used it incorrectly, then the component should have a very good case for claiming that it was really the service that malfunctioned. It's like two components blaming each other for the error. One exception could be if you know that the service incorrectly raises a client error in cases where the error is really a service error. For instance, an SQL driver complaining incorrectly over incorrect SQL, if the database connection is broken. In such a case you could change the client error classification to service error. But these cases are rare. |
Service Errors |
Service errors are rarely reclassified. If you call a component, and that
component raises a service error, then the called component often knows best
if the service it uses failed or not.
Exceptions to this is if you know that a component incorrectly classifies a certain error as a service error, when it is really something else. For instance, if a database driver is requested to connect to a given database, and the database does not respond, there are two options:
|
Internal Errors | Internal errors are rarely reclassified. If a component states that some error is an internal error,
the called component often knows this best.
Of course there can be exceptions to this rule, just like with service exceptions. |
Summary
How an application is able to react to an error depends on what type of error occurred.
Your application should try its best to determine the type of the error. It is not always possible to determine this 100% correctly though. If in doubt, classify the error as an internal error. Once the error occurs in production, you may learn more about the circumstances in which it occurs, and be able to change the classification logic.
Tweet | |
Jakob Jenkov |