IT Prescription
Curing What Ails Today's IT
Information Technology has exploded so quickly and IT departments have sprang up everywhere. But a lack of discipline and organization is pervasive and it is time to make IT run more effectively.
IT Process Improvement
Making More Effective Software
Java, SQL, JavaScript and more
Standards for Success
Over the years, it has amazed me how people respond when talking about standards...

Some people love them. They see the standards as guidance from the experts. They help focus the efforts of many different people so everyone is working in harmony. Standards help us follow the best practices of experience without having to go through the pain of discovery ourselves.

The people who love standards are usually organized team players. They are looking for direction, efficiency and predictability. They want to cut to the quick and get the job done. They would rather not spend a lot of time reinventing the wheel.

On the other side of the fence are the people who hate them. The people who hate standards see them as oppressive. They stifle creativity. They tell us what someone else thinks is best and restrict us from doing what we know is better. Standards hold us back.

The people who hate standards are typically intelligent and independent or creative thinkers. They see standards as an obstacle in the way of them doing what they know is right. Many times these opponents have years of experience behind them and draw from that experience when making their arguments against standards.

Who is right and who is wrong?

The answer to this question is not cut and dry. Standards do help guide us and make it easier for less experienced people to do very complicated tasks. We live with many standards such as what side of the road to drive on, how we pay for goods and services (cash, credit or debit) and proper ways to greet someone new. Some of these standards are very strict and, if not followed, can result in people being hurt or killed. Others are very loose and need to be altered depending on your current situation.

In my experience, I find standards are a great place to start. In most cases (roughly 80% - 90% of the time) it is much easier and practical to follow the standards. When this is not the case, the standards quickly fall out of fashion and disappear in obscurity.

When you are put into a situation that you have never been in, the only guidance you have are the standards set by those before you. Why not start there? Then alter your approach only when you find alternatives that are superior. Often times, when you find a new and better alternative, it may eventually become a new standard.

One day, when I was going through one of my mailing lists, I found a post by a user new to to subject. They were asking about the best way to perform a common task. They had heard about the standard way to achieve their goal but they were unsure about why. Was this standard really the best approach?

A great deal of discussion ensued, including a bit of frustration from the more experienced members of the mailing list. One person said that this new user should go with the standard - it's the way everyone else does it. In this example I agreed, the standard approach was truly the easiest and most practical. But I disagreed with the reason.

A standard gives us guidance and it is where we should start when we are new. But it is the people who question the standards that help us improve upon them and open our eyes when the standard is deluding us from the real truth.

An example:

In the late 19th century scientists believed in a luminiferous aether and used the concept to explain many properties of light they did not understand. Unfortunately, this belief held back scientific advancement. It wasn't until advancements in scientific theories like Albert Einstein's special theory of relativity came about that the concept of an aether became obsolete.

It is the people who question the standards that help us make the big advancements. But we must not forget the value of the standards in our lives. They give us a starting point. The more standards we have to begin with, the better off we will be when starting something new. We can do more, faster, better and cheaper when we use standards.

At the same time we must not forget to question what we are told. I am not advocating rebellion in every aspect of our lives. Instead, I prefer to follow as many standards (and rules) as possible and then pick out those standards that I feel are holding me back from greater achievements. I focus my efforts on the standards that, if broken or changed, provide me with the greatest benefit.

For those people who feel it is their prerogative to rebel against all standards imposed upon them, they will find an endless supply of oppression and exhaust themselves with their struggle. Like a person who takes a giant piece of plywood and stands it up against a river to block the flow as they cross to the other side, they will find themselves in a loosing battle. The river will continually press down on them when all they had to do was turn the sheet of plywood with the current and let the water effortlessly flow on by - or better yet, hold it above their head.

Where do we stand to gain from standards?

The list below contains many of the benefits you and your company can gain by using standards.
No reinventing the wheel:
By using good standards you only need one person to create and determine how to implement a great idea. Then everyone can use it over and over with little to no additional effort.
Better communication between colleagues:
If everyone is using the same standards, they all know how other team members operate. Standardization of language is just an example. If you standardize on operating procedures, it enables a person to just say a few words and others immediately understand the context of the situation and can immediately jump in and help.
The ability for people to switch between projects quickly and easily:
This helps the business and its employees. The business benefits by having the flexibility to move their employees to where they are needed without high costs in time and money. The employees gain further job security since they can do more for the business and, if their job becomes obsolete, they can move to other positions.
Quicker training and set up of new employees:
When you standardize on the tools people use and how they use them, it becomes much easier to bring in new people and have them working productively right away.

I recall one job I had at a local marketing firm. They believed that their developers should have the freedom to choose whatever tools they wanted to use and install software on their computer however they wanted. I also remember that when I started, they paid me for two weeks to wrestle with an old machine set up by a previous employee. I got nothing productive done for 2 weeks and I estimate they spent over $6000 for ... nothing. To top it off, I left in frustration (just about everything there was as wasteful as this) after working for them for only 2 months so they had to do it again with someone else.
Easier to consolidate the knowledge for best practices and ways to avoid or fix problems:
If everyone is using the same tools and following the same procedures, when people have a problem it is highly likely someone has experienced the same problem before and your colleagues can help each other. And if you are continually updating your standards you can implement changes that prevent the problem from occurring again in the future.
Systems are easier to connect:
With standards it becomes easier for other departments to communicate and easier for their systems (either physical or procedural) to interoperate efficiently.
Less of a need for highly skilled and expensive labor:
If your business is growing, this growth typically increases the complexity of the inner workings of the business. Such complexity requires more highly skilled labor. If you set good standards you can reduce the complexity and lower the skill sets required to grow. This doesn't have to lead to employees loosing their jobs. If the few highly skilled employees you have are responsible for creating the standards, you want to keep them around. But the new people you hire need not be as skilled or as expensive.
New products and services or efficiencies are easier to realize:
Once you have everything organized, you will find it easier to make changes and communicate those changes throughout your organization. If a new product or service is very similar to another it is a lot easier to develop the product or service by simply copying an already well organized idea. It is like a factory where creating large number of similar but highly complex parts is easy to do, costs very little money and can be done with high reliability. It is also easier to estimate time lines and costs for the new products and services.

What problems should we watch out for?

The following is a list of the potential problems, either with the perceptions people have of standards or with the way they are implemented and maintained. To get the most out of the standards you and your company abides by, you need to be aware of the pitfalls.
Standards are the bane of the creative mind:
Your most creative employees will often resist standards. Creativity is thinking out of the box while standards are working within the box. You need to find a way to encourage your employees to abide by the standards but always be thinking of ways to improve upon them. Also, everyone should realize that you cannot follow 100% of the standards 100% of the time. I like to use the 80/20 rule: if you stick to about 80% of your standards, it will account for about 20% of your time. And the other 20% that you deviate from the standards will take up 80% of your time. This demonstrates how expensive it is to go against the standards but to stay competitive, it is necessary. If everything you do is standard, you will find it hard to distinguish yourself from your competitors.
Standards prevent us from doing what needs to be done:
It is true that standards can sometimes get in the way. If you find that employees are not following a standard, it is time to review the standard. You may need to alter or remove the standard all together. Don't get into the trap of using a standard just because "it has always been that way".
Standards become stale and no longer relevant:
Periodically go back and review your standards. Times change. Technology changes. Business changes. Consumers want something new. You need to keep your company fresh and on the cutting edge. Upgrading your standards is one way to ensure your business stays on top.
I don't want anyone telling me what to do!
When I hear something like this from an employee, I immediately see someone who is NOT a team player. It takes people who work together to create truly great products and services. If an employee is going to be rebellious like this, you should question whether they belong on the team.

MORE >>
Posted by Aaron Bono at 7/25/2006 11:27 AM | View Comments (0) | Add Comment | Trackbacks (0)
It's an IT Revolution
One day, several years back, I went to a meeting with a number of other programmers to learn more about how software was developed and tools used at our company. It was a rather large company having hundreds of developers devoted just to the company's web site. I was eager to find out how I could develop software better and spend less time on the annoying and mundane things like logging and connecting to databases.

What I found at the meeting was that the company was standardizing on a language and the tools we use, but they really had no idea where they were going. They had no standard code libraries, no standard machine configurations and no advice for me on how I could do my job better. The IT department lacked leadership. Everyone wanted to do things their way with little regard to what was best for the company. And there was a distinct lack of standards everywhere - even the mention of the word standards raised people's ire.

After the meeting I was frustrated. Two hours spent and nothing to take back to help me do a better job. I complained to one developer that I wanted to know the standard way the company recommends you connect to the database. After all, everyone was supposed to be using Java, WebLogic, EJBs and Oracle. I should be able to get a boiler plate file from SOMEONE that simply required me to change the database name, schema name and password and be ready to go.

The response from this developer: I don't want the company telling me how to program! That takes the fun out of it!

And here in lies the problem. Developers love a puzzle. They want things to be hard. It is fun. It makes them feel knowledgeable and capable. They feel they are among the brightest in the world. The difficulty of the job justifies their high pay and job security while giving them the challenge they seek in life.

But this is not necessary...

It is a matter of time before businesses grow weary of spending so much money on IT staff when there is a better way. Everyone has been brainwashed into thinking technology is hard. Software is hard. Computers are hard. Well, the things that are IT are only hard because we make them that way.

It is time for an IT revolution!

In the book The Inmates are Running the Asylum by Alan Cooper (which is a very good book that I highly recommend any IT manager read), the author states that "No company can treat programmers the same as a factory. Programmers demand continuous attention and support well above that of any factory." The author is very effectively teaching us how IT is backwards and structured all wrong but shows that he is still brainwashed into thinking software is hard to build. This is the one and only point I have read in this book where I disagree. You can turn your software development into a software factory. It is not necessary for software to be difficult.
By the way, Alan Cooper does a wonderful job showing the reader where software development is dominated by the programmer and how this damages the end product. He explains how to break out of this and add what he calls the "interaction designer" to make highly superior user interfaces.
There is a way to make IT simpler. I believe, in the near future, we will find out how to organize and manage IT, in many ways, just like a construction company handles the design and construction of a building. When you build a large building, you start by designing with an architect and an interior and exterior designer. These highly paid experts decide how the building will look and be built. After this is done, you bring in the lower paid technicians to do the work.

So what does this mean for the programmers? It means they will become the technicians. It means they will no longer be able to demand the high salaries. And it means the business owner will begin to regain control over their business and not be such a slave to the developer.

NO! you cry. You cannot belittle my role, my intelligence or my importance in the building of software.

You are correct...

The best and brightest software developers will slowly become (or maybe even have become) software engineers. They will employ strong standards much like aerospace and mechanical engineers. They will be the architects and the creators of the new standards. They will create a simple foundation so that the software developers (or technicians) can do their jobs simply and easily. The software developers will be the construction workers and the software engineers will be the architects. And the business will need very few engineers and many developers.

Why is this approach inevitable? Because it is more reliable and cost effective. Today, all too many software projects are failures. This costs businesses lots of money. And developers are expensive. This costs businesses even more money. The first business that figures out how to cut the costs, increase the likelihood of success and improve the reliability of their products will propel themselves well ahead of their competition.

Over the coming months I will expound on this topic and demonstrate how IT organizations can realize cost savings through this new IT Revolution. And I will show developers how they can stay on top by adopting standards, using the best tools and elevating themselves to the level of software engineer.

May your journey be prosperous.

MORE >>
Posted by Aaron Bono at 7/19/2006 12:50 PM | View Comments (0) | Add Comment | Trackbacks (0)
Error Handling - Ignore, Hide or Run Away

Disclaimer: Note that throughout this article I am use System.out and/or System.err to send error messages to the user. In a real application you should NOT use System.out or System.err unless you are working with a command line application and even then you may not want to use them. Instead you should use a logging utility. See my previous article on Java Logging Made Easy for information about how to replace your System.err and System.out with good logging.

Throughout my years of college, taking programming training courses and tutoring, I have found a never ending deluge of bad programming practices. Most of these bad practices stem from the fact that the instructor feels they don't have the time to teach everything so they teach what they need to. Unfortunately, every programming course I have taken or tutored for glosses over or simply ignores a very critical element: error handling.

I once heard that error handling can make up as much as 50% of the code in an application. Personally I have found this estimate to be a bit high but I would not be surprised if at least 25% of my code is devoted to error checking and handling. I can definitely agree that about half of the user interface code is dedicated to error checking and handling.

For this article I am not going to go into error checking. Error checking is the code that makes sure required fields are filled in, valid data is typed in by the user and severe errors do not occur. Error checking is proactive programming, where the programmer anticipates what could go wrong and either prevents the actions from being performed or warns the user of their invalid actions. I see this taught, to a certain degree, in classes and the methods for doing error checking vary depending on the user interface. I will show some nice error checking code in later articles when I cover Swing, Struts or other GUI focused topics.

Error handling is a more reactive approach to dealing with errors in your system. In a perfect world, you never have to be reactive. All errors are checked and dealt with properly and the system runs smoothly.

We don't live in a perfect world. Moreover, your boss is probably never going to give you the time to make the perfect system.

So, even though we want to be proactive and never have to mess with error handling, it is a necessity. Good error handling helps us uncover bugs we never thought could occur, discover their source and fix them quickly.


What is an Exception

Exceptions are ways for the code to tell you, "Hey, there is something wrong here!" In the old C days, functions would return error codes to let you know if something is wrong. If the error code indicated everything ran smoothly, you could continue without pause. Otherwise you had to compare the error code with a list of possible problems to see what really happened. There are a few problems with this approach.

The first problem is that, since you can only return one thing from your function, you cannot return something useful when errors don't occur (which should be the majority of the time). If I called a function like openFile it would return an error code. But what I really wanted was the file handle so I could start reading the contents of the file. So the function had an additional argument where the file pointer could be stored. This is more of a hack or work around.

The second problem is that developers often ignored the error. They call the functions and don't even grab the error code or ask if the function call even succeeded. It took self discipline to write code with proper error handling.

The third problem was the fact that you had to have good documentation of all the possible things that could go wrong. Then, in your program, you would have a case statement of if/else blocks to test each possibility. If you forgot one of the error codes, you didn't find out until later when everything went awry.

However, with exceptions you list out the things that can go wrong in the method signature. For example:
public User logInUser(String userName, String password)
	throws InvalidUserException, InvalidPasswordException, 
	LoginNotAvailableException;
Immediately with this syntax you can see the three things that can go wrong when attempting to log in AND you the programmer are forced to deal with it. The method also returns the user object which is what you were wanting in the first place.

In Java, you have two options when dealing with exceptions: you can handle it or you can pass it on. Here are two examples:
// Handling an exception
public void logIn() {
	User user = null;
	try {
		user = logInUser("admin", "password");
		System.out.println("Logged in as " + user.getFirstName());
	} catch (InvalidUserException e) {
		System.err.println("Invalid User");
	} catch (InvalidPasswordException e) {
		System.err.println("Invalid Password");
	} catch (LoginNotAvailableException e) {
		System.err.println("Unable to log in");
	} finally {
		System.out.println("Done");
	}
}
// Passing an exception on
public void logIn() throws InvalidUserException, InvalidPasswordException,
LoginNotAvailableException {
	User user = logInUser("admin", "password");
	System.out.println("Logged in as " + user.getFirstName());
}
In the first you see that we have to catch each exception. We could have also just done a single catch (Exception e) to catch them all. Note that every try needs one catch or one finally. Each try can have multiple catch blocks, each catch for a different exception, and can only have one finally.

Note also that if anything goes wrong in the try block, execution of the try ceases immediately and any code that remains unexecuted in the try is skipped. If the exception is caught, the catch block is run. The finally is run regardless of whether an exception is thrown or not. As you will see in the examples later on, the finally block is an ideal place to put code that closes IO streams and database connections. Consider it your clean up spot.

In the second logIn() we just declare the exceptions in the throws clause and make anyone who uses the logIn() method deal with the problem.

In Java, there are several classes you should be familiar with when it comes to error handling. These classes are Throwable, Error, Exception and RuntimeException. The class diagram for this is shown below.



At the top is the Throwable. You can only throw objects that are subclasses of Throwable. The throwable provides two things that are highly useful to the programmer: a message and the stack trace. The message tells us, if the code throwing the exception was well written, what the problem is. The stack trace lists out the stack of code that was running when the exception was thrown with the names of the classes, methods and line numbers. It is with these two items that I fix about 99% of the errors in my applications that cause exceptions to be thrown.

The two main subclasses of Throwable are Error and Exception. Errors are abnormal conditions and indicate serious problems. The Java Docs state that Errors do not need to be declared in throws clauses or be caught. In fact, the Java Docs go as far as to state that you SHOULD NOT attempt to catch errors. Typically errors are fatal to the application and are most often caused by someone killing a Java process. You will also see errors if you use JNI and your C++ code has errors.

The Exception class is what we will focus on here. It is what you, the programmer will see and have to deal with. The exception has many subclasses but the one you should be most familiar with is the RuntimeException. Anything that extends Exception but does NOT extend RuntimeException must be either caught or declared in the throws clause of the method where the exception appears. Anything that extends RuntimeException does not need to be caught or declared in the throws clause (this is a good and a bad thing as you will see below).


The Typical (and Bad) Error Handling

Most new and, unfortunately, some experienced developers go about handling errors the wrong way. Here are the three ways I have seen errors dealt with and an explanation of why they will cause you grief. I call them the Ignore, Hide or Run Away approaches.

Ignore (aka Bite You In The Ass)

In Java the RuntimeException is one that can be ignored. It includes exceptions like NullPointerException, ArrayOutOfBoundsException and IllegalArgumentException. You can find many RuntimeExceptions in the java.lang package.

RuntimeExceptions can be thrown in a method without the method declaring it in the throws clause. For example:
// This is an example of how NOT to use exceptions
public void checkIntString(String args) {
	for (int i = 0; i < args.length(); ++i) {
		if (! Character.isDigit(args.charAt(i))) {
			throw new IllegalArgumentException("Character at position " + i + " is not a digit");
		}
	}
}
The reason for having RuntimeExceptions is that, without them, you would need try/catch or throws almost everywhere. Every time you use the . operator a NullPointerException can be thrown. Every time you access an element of an array an ArrayOutOfBoundsException can be thrown. The RuntimeException makes sure you don't have to put try/catch blocks everywhere. You can say that the try/catch is the exception and not the rule... ok, enough of the puns.

The problem with RuntimeExceptions is that anyone who calls our checkData(args) method above doesn't know they need to deal with possible errors. It is convenient for the programmer because they don't have to add error handling code but problems will begin to pop up once the system is put into use. At that time, the users have to deal with the errors. In many cases the application begins to act erratic or even crash. Convenient for the programmer, frustrating for the end user.

Instead, you should use non-RuntimeExceptions whenever possible. This forces the programmer to address potential errors during the coding process and does not drop the bomb into the user's lap.
// This is an example of how to use exceptions
import java.text.ParseException;

...

public void checkIntString(String args) throws ParseException {
	for (int i = 0; i < args.length(); ++i) {
		if (! Character.isDigit(args.charAt(i))) {
			throw new ParseException("Character at position " + i + " is not a digit", i);
		}
	}
}
When .NET first came out I purchased a book about C#. I was appalled to find that every exception in .NET is a RuntimeException (or at least the C# equivalent). What this means to me is that we want to make life easier for the programmer and force the testers, or worse the users, to find the errors. If you use non-RuntimeExceptions, you will be forced to deal with error handling just to get the program to compile. The compiler will tell you where to put the error handling, you won't have to go searching all over for potential problems. Wait a minute... this is being proactive... good for us Java programmers!

I didn't finish exploring this .NET "feature" so there may be more to the story so any of you .NOT developers out there, let me know if I missed something.

Hide

The most common way I have seen inexperienced developers handle exceptions is via the hide approach. This is a very simple and potentially disastrous way to deal with exceptions.

All non-RuntimeExceptions must be either declared in the throws of the method signature (as seen in the second checkIntString example above) or caught. To hide an exception you just wrap the offending code with a try/catch and leave the catch empty. For example:
// This is an example of how NOT to handle exceptions
import java.sql.Connection;
import java.sql.DriverManager;

...

public Connection getConnection(String url, String user, String password) {
	Connection conn = null;
	try {
		conn = DriverManager.getConnection(url, user, password);
	} catch (Exception e) {
	}
	return conn;
}
I ran into this one once in a production environment. A developer had written an application in Java and was no longer with the company (after reviewing their code I felt this was a good thing). Many months later, the database was moved to a new server and suddenly the application stopped working. It was giving an odd NullPointerException in a completely different part of the application and none of the people maintaining the application could figure out why. After several hours of agony they brought me in. They got me a copy of the code and I spent an hour or two digging around and trying to understand what was written. I finally found the problem was that the IP address of the database had changed and the getConnection method was failing. Of course the exception was ignored as if everything was OK when in fact everything was NOT OK. If this exception had not been ignored, the problem would have been immediately identifiable and fixed in minutes. Once everyone's time was added up, the error cost about $1500 in labor to fix and who knows how much money to rewrite the code so it handled errors properly. If the developer had coded it properly at the beginning it would have taken him minutes to write something that would have saved the company thousands of dollars.

Run Away

In the second checkIntString example above I showed you how you can declare the exception in the throws part of the method signature. I consider this good practice when writing utility classes or APIs that will be used in many places or by many developers. But you don't want to get carried away with it.

For example:
// This is an example of how NOT to use exceptions
public abstract class Task {
	public abstract void setArgs(String[] args) throws Exception;
	public abstract void checkUsage() throws Exception;
	public abstract void setUp() throws Exception;
	public abstract void doTask() throws Exception;
	public abstract void shutDown() throws Exception;
	
	public static void main(String[] args) throws Exception {
		Task task = (Task) Class.forName(args[0]).newInstance();
		task.setArgs(args);
		task.checkUsage();
		task.setUp();
		task.doTask();
		task.shutDown();
	}
}
This is the opposite extreme from the hide method. Everything just throws Exception. I have seen developers put throws Exception in every method just like this, even if there is no need for the exception. It certainly makes it easier to code but notice the problem - no one is handling the exception. It just bubbles on up to the top and crashes the program. You should throw meaningful exceptions like this:
public class TaskException extends Exception {
	public TaskException(String message) {
		super(message);
	}
	public TaskException(String message, Throwable cause) {
		super(message, cause);
	}
}

public abstract class Task {
	public abstract void setArgs(String[] args);
	public abstract boolean isUsageCorrect();
	public abstract String getUsageMessage();
	public abstract void setUp() throws TaskException;
	public abstract void doTask() throws TaskException;
	public abstract void shutDown() throws TaskException;
	
	public static void main(String[] args) {
		Task task = null;
		try {
			task = (Task) Class.forName(args[0]).newInstance();
		} catch (Exception e) {
			System.err.println(e.getClass().getName() + " creating task");
			e.printStackTrace();
			System.exit(-1);
		}
		
		task.setArgs(args);
		if (! task.isUsageCorrect()) {
			System.err.println(task.getUsageMessage());
			System.exit(-2);
		} else {
			try {
				task.setUp();
			} catch (TaskException e) {
				System.err.println("Error setting up task");
				e.printStackTrace();
				System.exit(-3);
			}
			try {
				task.doTask();
			} catch (TaskException e) {
				System.err.println("Error running task");
				e.printStackTrace();
				System.exit(-4);
			}
			try {
				task.shutDown();
			} catch (TaskException e) {
				System.err.println("Error shutting task down");
				e.printStackTrace();
				System.exit(-5);
			}
		}
		System.exit(0);
	}
}
Yes it is a lot more code but when it is run by a user, they will get a meaningful error message if there is a problem. Note that the main method does not throw an exception. I recommend that your main method should never throw an exception, you should always handle the error and then exit if necessary.


The Right Way To Handle Errors

Now that we know how NOT to handle exceptions, what are some rules on the proper ways to handle them. Here's how we really should do it...

try/catch or throws?

The first thing you need to decide when dealing with an exception is whether to use a try/catch or declare it in the throws clause. The answer to the question is going to be rather subjective but I have some guidelines for helping you decide which approach to use.

If you are working with an API to be used by other developers, you should almost definitely declare the exception in the throws clause. You may want to catch any exception and wrap it with your own custom exception (see below for how this works) and this custom exception will then appear in the throws clause. Regardless, throwing an exception is a great way to communicate to other developers that they need to be aware that problems can occur and they need to be ready to deal with them and inform the user.

As with an API, if it is not clear what will need to be done with the exception when it arises, you will probably want to declare it in the throws clause. However, make sure that somewhere, eventually, the exception will be handled.

If the exception pops up at a place where you know how you want to handle it, that is the perfect place for a try/catch. Catch the exception, handle it appropriately and move on.

For example, let's say you have a web site that consists of JSPs, Servlets, Business Logic objects and DAOs. The JSPs render HTML. The user then clicks on links or submits forms to the Servlets. The Servlets tell the Business Logic what to do, the Business Logic tells the DAOs to do inserts, updates or deletes and the DAO runs the SQL against the database. This is common in 3-tiered web applications. If there is an error on the database, perhaps you are trying to add a user but that user name is already taken, the DAO will receive an SQLException. What I do in this situation is have the DAO pass the exception on up to the Business Logic. The Business Logic then wraps the exception with its own BusinessLogicException (or maybe even a UserExistsException) and throws that to the Servlet. The Servlet then catches the exception, prepares a nice error message for the user and sends the message to the JSP which then displays it to the user.

In this example, the DAO had no idea what to do with the exception. It did not know if the caller was a web site or a desktop application. So, it dumbly passed the exception on up the chain.

The Business Logic also didn't know what the caller was. Keep in mind that, in order to maximize reuse, we want to design the Business Logic to be usable by a desktop application, by another web site or maybe even a web service. The web site doesn't need to know we are using a database. We could be using Active Directory, a SOAP service or a simple file. So the Business Logic needs to transform the SQLException into some generic business error - so we catch the SQLException, wrap it in a BusinessLogicException and throw the BusinessLogicException.

Once we get to the Servlet level, we now know what we want to do with the exception. we extract the message from the exception, log the error and send the user back to the page they were on and display the error message.

In general I attempt to throw the exception (or the wrapped exception) up to the user interface level. Ultimately the error needs to be presented to the user so, in most cases that is the most logical place to catch the exception. On a desktop application you can then display an error dialog to the user.

You should be specific when declaring an exception in your throws clause. Don't throw Exception unless you have to. Some good examples of where you may consider using throw Exception are in interface methods where you really don't know what may go wrong in specific implementations - even then you may want to create your own special exceptions).

What to Put Where

The throws clause is pretty straight forward. You simply declare each exception thrown within the method declaration.

With the try/catch/finally there are many choices and quite often inexperienced developers choose poorly.

Before the try block you should declare any variables you may need in your catch or finally blocks. Initialize their values to null in the declarations. If you are doing file IO, you should declare your IO streams. If you are connecting to a database, you should declare your connection, statements and result sets.

Next, put the rest of the code that is used if there is no exception thrown into the try block. If any of the code at the end of your try block will need to run regardless of whether an exception is thrown or not, you should move that code to the finally.

Last you should add your catch blocks and add the code that handles each exception caught.

Example: here is a utility class that checks the contents of a text file to see if a specific string is present on any one line in the file. The method will notify the user about any errors but will just return false if it has problems reading the file. In many situations you would declare the IOException in the throws clause and remove the catch block.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

...

public boolean fileContains(File file, String str) {
	// Declare the variable needed in the try and finally
	BufferedReader reader = null;
	try {
		reader = new BufferedReader(new FileReader(file));
		String line = null;
		while ((line = reader.readLine()) != null) {
			if (line.indexOf(str) >= 0) {
				return true;
			}
		}
		return false;
	} catch (IOException e) {
		// Let the user know there was a problem.
		System.out.println("Error reading file:");
		e.printStackTrace();
		return false;
	} finally {
		// No matter whether the try succeeds or fails,
		// we should close the file - for this reason we
		// close the BufferedReader in the finally.
		// Note that even though the try and catch blocks
		// have a return statement, the finally will run
		// BEFORE the value is actually returned.
		try {
			if (reader != null) {
				reader.close();
			}
		} catch (Exception e) {
			System.err.println("Error closing file read");
		}
	}
}
If you are putting a throws into your method declaration, you should still consider using a try/finally. For example:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

...

private String dbUrl;
private String dbUser;
private String dbPassword;

// Attempt to save the user to the database.
public void saveUser(String userName, String password, String firstName, String lastName)
throws SQLException {
	Connection conn = null;
	PreparedStatement stmt = null;
	try {
		conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
		stmt = conn.prepareStatement(
			"INSERT INTO app_user (" +
			"	user_name, " +
			"	pass, " +
			"	first_name, " +
			"	last_name" +
			") VALUES (" +
			"	?, ?, ?, ? " +
			") "
		);
		int index = 1;
		stmt.setString(index++, userName);
		stmt.setString(index++, password);
		stmt.setString(index++, firstName);
		stmt.setString(index++, lastName);
		stmt.execute();
	} finally {
		// Close the statement and connection.  Note that
		// we close the statement separately from the
		// connection so that, if there is a problem closing
		// the statement, it doesn't stop us from closing
		// the connection.
		try {
			stmt.close();
		} catch (Exception e) {
			System.err.println("Error closing statement");
		}
		try {
			conn.close();
		} catch (Exception e) {
			System.err.println("Error closing connection");
		}
	}
}
Also make sure you put your catch blocks to work. At the very minimum, most if not all of your catch blocks should have some kind of logging. If the exception caught is rather benign, you can log at a debug or info level (see my Java Logging Made Easy article for details on logging). If it is important that we take notice of and deal with an exception we should log at the error or at warning level.

Example: Here is a utility that attempts to delete a temp file. Since, if the delete fails, it really doesn't create an immediate problem, we just want to log it and continue on without stopping the normal operations of the program. Someone will check the logs later and find that the temp file was not deleted properly and fix the problem then.
import java.io.File;

...

public void deleteTempFile(File file) {
	try {
		file.delete();
	} catch (Exception e) {
		System.err.println(
			"Unable to delete temp file " + file.getAbsolutePath() + ": " + e.getMessage()
		);
	}
}
A user interface will want to collect errors and display them to the user. Sometimes one error is fatal enough to stop execution and display the error right away but at other times you may want a list of errors to show the user.
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

...

private List errors = new ArrayList();

private String formatPhoneNumber(String phone) throws ParseException {
	StringBuffer numbers = new StringBuffer();
	for (int i = 0; i < phone.length(); ++i) {
		char c = phone.charAt(i);
		if (Character.isDigit(c)) {
			numbers.append(c);
		}
	}
	if (numbers.length() != 10) {
		throw new ParseException("Phone number has only " + numbers.length() + " digits", phone.length() - 1);
	}
	numbers.insert(0, '(');
	numbers.insert(4, ')');
	numbers.insert(8, '-');
	return numbers.toString();
}

public String[] formatPhoneNumbers(String[] phoneNumbers) {
	errors.clear();
	List formattedNumbers = new ArrayList();
	for (int i = 0; i < phoneNumbers.length; ++i) {
		try {
			formattedNumbers.add(formatPhoneNumber(phoneNumbers[i]));
		} catch (ParseException e) {
			errors.add(e.getMessage());
		}
	}
	return (String[]) formattedNumbers.toArray(new String[formattedNumbers.size()]);
}

public String[] getErrors() {
	return (String[]) errors.toArray(new String[errors.size()]);
}
Some software has built in ways to accumulate and display errors to the users. For example, Struts allows the web application to build up a list of errors and even associate those errors to particular fields on the web page. This allows you to display error along side the field where the error is occurring making it easier for the user to discern what is wrong and where and quickly fix it.

Then there are times you need to wrap an exception. If we go back to the Task and TaskException in an earlier example, let's say we want to create a Task that starts at a given directory and determines the size of all the files in that directory and all the directories beneath it and prints the total size to the user. If there is an error, a TaskException will be thrown. Although the SecurityException is a RuntimeException and we could just let it go, we really should catch it and wrap it in a TaskException so the Task library will handle the problem properly.
import java.io.File;

...

public class DiskUsage extends Task {
	private String[] args;
	
	private File dir;
	
	public void setArgs(String[] args) {
		this.args = args;
	}
	
	public boolean isUsageCorrect() {
		return (args != null) && (args.length == 2);
	}
	
	public String getUsageMessage() {
		return "Usage: java Task MyTask [directory]";
	}
	
	public void setUp() throws TaskException {
		dir = new File(args[1]);
	}
	
	public void doTask() throws TaskException {
		long size = getSize(dir);
		System.out.println(
			"The directory " + dir.getAbsolutePath() + 
			" and its subdirectories contain " + size + " bytes"
		);
	}
	
	private long getSize(File directory) throws TaskException {
		long size = 0;
		try {
			File[] files = directory.listFiles();
			for (int i = 0; i < files.length; ++i) {
				if (files[i].isDirectory()) {
					size += getSize(files[i]);
				} else {
					size += files[i].length();
				}
			}
		} catch (SecurityException e) {
			// Note we cannot catch Exception as that will grab the
			// TaskException if thrown by our recursive call to getSize.
			// The Java Docs for java.io.File tells us that the methods
			// listFiles(), isDirectory() and length() can throw
			// SecurityException which is actually a RuntimeException
			throw new TaskException(
				"Error attempting to get size of directory " + directory.getAbsolutePath(), 
				e
			);
			// Note also that we put the SecurityException INSIDE of
			// the TaskException so when we print out the stack trace
			// we get the stack trace from both exceptions and can
			// see where the problem started.
		}
		return size;
	}
	
	public void shutDown() throws TaskException {
		// Nothing to do
	}
}
Why wrap exceptions? Why not rethrow the same exception or, as we could have done in the last example, just let the RuntimeException go?

First of all, when you are writing code that extends or implements code from another API, you should always follow their exception handling. If you do not, you can get unpredictable results. What would have happened if we had let SecurityException go without wrapping it with TaskException? After all, Task was expecting a TaskException, not a SecurityException. In our case, Task would NOT have handled the exception properly and the program would have unexpectedly terminated. You should try to throw exceptions that the caller expects.

You should also NEVER rethrow the exception you caught:
// NEVER do this
import java.io.FileInputStream;

...

try {
	FileInputStream in = new FileInputStream("test.txt");
	
	// More code here...
} catch (Exception e) {
	System.err.println("Error creating input stream...");
	e.printStackTrace();
	throw e;
}
Here the problem is that, as soon as you say throw e your exception's old stack trace is replaced with a new stack trace. The result is that you have no way to recover the exact location where the original error occurred. Without that information your attempt to find and fix the bug may turn into a crap shoot. Instead, WRAP your exceptions.
import java.io.FileInputStream;

...

public class MyException extends Exception {
	public MyException(String message, Throwable t) {
		super(message, t);
	}
}

...

try {
	FileInputStream in = new FileInputStream("test.txt");
	
	// More code here...
} catch (Exception e) {
	System.err.println("Error creating input stream...");
	e.printStackTrace();
	throw new MyException("Error creating input stream", e);
}
Now when the MyException is finally caught and a stack trace is produced, you will see not only the stack trace for MyException but you will also see the stack trace for Exception e stored inside it.

I hope this has provided you with an arsenal of techniques to help you do your exception handling like an expert. With the proper exception handling, you will experience fewer support issues and have much more stable applications.

MORE >>
Posted by Aaron Bono at 6/28/2006 7:17 PM | View Comments (0) | Add Comment | Trackbacks (0)
Java Logging Made Easy
Not long ago I joined a friend of mine for lunch to discuss what we were both doing in our new jobs. My friend had recently started a job with a local IT training company teaching several topics including a few Java courses.

"So what kind of logging are you using?" I asked. After all, you need some kind of output to do quick and easy debugging. The debugger is nice but is sometimes more cumbersome than it is worth.

"We are just printing to standard out."

I quickly inquired why they couldn't use a standard logging utility like Log4J or the built in java.util.logging. I have always been a stickler for teaching students how to do things right the first time. So why teach them to log using System.out when that is the last thing you do in a real world application? To my friend it was a matter of time. They just didn't have the time to teach the students how to do proper logging and cover all the other topics in the class.

But I had a solution - a simple approach to logging that would be easy for novices to learn and would be easy to convert to more sophisticated logging in the future.

What I suggested was to use a logging wrapper. Have the students start out by creating a XYZAppLogger class that all their code calls when they need to log. Then have the XYZAppLogger dump everything to System.out or System.err.

What are the advantages of doing this?
  • The XYZAppLogger, as you will see below, is a very simple class so it helps new students get started by teaching them to type in, compile and run a program. This is a trivial task for experienced Java programmers and a nice intro for beginners.
  • By having all their code call XYZAppLogger, when a student is ready to start using a sophisticated logging utility, they only change one file - the XYZAppLogger.
  • If a student decides they don't like the logging utility they choose, they can easily switch to something else. I had a programmer tell me once that they thought it was a waste of time to use a logging wrapper. "Just call the logging utility directly. You will never change your logging." Two months later I sat at my computer coverting all my code from java.util.logging to Log4J. I knew better - so why did I listen to that other programmer? I use a logging wrapper for all my logging now, just in case.
  • Turning logging off throughout the whole application is easy - you just comment out the System.out.println and System.err.println statements in the XYZAppLogger
  • With a simple if/else in the XYZAppLogger you can turn logging on or off for certain classes. We will leave this one as an exercise for the instructor - yes we are giving you an exercise!
  • You teach students that the logging is worth keeping in your application. Without this logging mechanism, students add the System.out, do their debugging, delete it (or comment it out), and then have to re-add (uncomment) it later if they need to do further debugging. Good God man! Don't delete it - that logging is valuable!
Below is a detailed approach I would recommend using to teach logging to a class learning Java, whether they are new to the language or experienced developers. It starts out simple with the intention of setting up a foundation for good logging without taking much classroom time.

After showing how to set up basic logging, I show you how to convert it into Log4J. This includes not only how to replace the System.out and System.err but also how to load your logging settings dynamically from your own custom properties files.


Logging for the Classroom

The essence of logging in your applications is that you want to give the programmer the power to send messages to the user when the user wants to see them. In order to give the user the power to dictate what messages they do and do not want to see, the experts have come up with concept of the logging level.

In Log4J, one of the most common logging utilities for Java, there are 6 logging levels:
  • Fatal
  • Error
  • Warn
  • Info
  • Debug
  • Trace
If you count ALL and OFF you get 8, but 6 is more than enough for beginners, maybe even too much. The order is important. If I turn Info logging on, I automatically turn on Warn, Error and Fatal. Anything at or above the setting I choose is on and anything below is off.

When I train programmers how to log, I first explain the 6 levels. When they ask, as the inevitably do, "How do I know which one to use?" I simply say, it's up to you, use your best judgement. The names of the logging levels are fairly self-explainatory. Beyond that, experience is going to be the best teacher. If they really want to know, I suggest digging through some of those open source projects like you find at apache.org.

OK, we spent one minute of the class, let's move forward, get the logging discussion done so we can cover the topics the students paid us to teach them.

The next level of control for the user over what is and is not logged revolves on what part of the program is generating the output. Since Java is loaded with lots of small class files (we won't debate whether that is a good or bad thing here), the class is a great way to control logging. Log4J also allows package level control but let's not burden the students with that for now.

So what does our XYZAppLogger look like? First it will need a method for each logging level:
  • public void fatal(String message)
  • public void error(String message)
  • public void warn(String message)
  • public void info(String message)
  • public void debug(String message)
  • public void trace(String message)
Then I throw in a few more so we can print out the stack trace of our errors:
  • public void fatal(String message, Throwable t)
  • public void error(String message, Throwable t)
  • public void warn(String message, Throwable t)
Second we need a way to identify what class the logger is logging message for:
  • public static XYZAppLogger getLogger(Class c)
We put all this together and get this:
public class XYZAppLogger {
	private String name;
	
	private XYZAppLogger(String name) {
		this.name = name;
	}
	
	public static XYZAppLogger getLogger(Class c) {
		return new XYZAppLogger(c.getName());
	}
	
	public void fatal(String message) {
		System.err.println(name + ": " + message);
		System.err.flush();
	}
	
	public void fatal(String message, Throwable t) {
		System.err.println(name + ": " + message);
		t.printStackTrace();
		System.err.flush();
	}
	
	public void error(String message) {
		System.err.println(name + ": " + message);
		System.err.flush();
	}
	
	public void error(String message, Throwable t) {
		System.err.println(name + ": " + message);
		t.printStackTrace();
		System.err.flush();
	}
	
	public void warn(String message) {
		System.err.println(name + ": " + message);
		System.err.flush();
	}
	
	public void warn(String message, Throwable t) {
		System.err.println(name + ": " + message);
		t.printStackTrace();
		System.err.flush();
	}
	
	public void info(String message) {
		System.out.println(name + ": " + message);
		System.out.flush();
	}
	
	public void debug(String message) {
		System.out.println(name + ": " + message);
		System.out.flush();
	}
	
	public void trace(String message) {
		System.out.println(name + ": " + message);
		System.out.flush();
	}
	
	public static void main(String[] args) {
		XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
		logger.fatal("Test fatal");
		logger.error("Test error");
		logger.warn("Test warn");
		logger.info("Test info");
		logger.debug("Test debug");
		logger.trace("Test trace");
	}
}
If you think this is too much for your students, no problem. Start with just the error(String message, Throwable t) and debug(String message) methods. They can add the others later. You can also ommit the flush() if all the methods use only System.out (or only System.err) but if you mix System.out and System.err, your logging may look odd because System.out and System.err tend to flush their output asyncronously causing your messages to be mixed togther and hard to read.

Now your students can build, compile, run and test their new logging utility. And they see an example of how to use the logging in the main method.

Excellent!


Switching to Log4J

When converting your logging over to Log4J, all you really need to do is replace the System.err and System.out statements, right? Well, almost. We will want to add a caching mechanism so we create fewer XYZAppLogger objects. We will also add methods to ask the logger if logging for that level is enabled or not.

First lets get the Log4J logger added. To do that we replace
private String name;
with
private Logger logger = null;
Oh, yea, what am I thinking! You should download the Log4J library from http://logging.apache.org/log4j/docs/download.html. Whew, can't forget that. Now where was I...

The constructor becomes
private XYZAppLogger(Class c) {
	this.logger = Logger.getLogger(c);
}
Now get rid of the ".getName()" from the getLogger method. With that we also need to replace all the System.out and System.err statements.

Next we want to add caching. We want to make sure that if the same class is passed into getLogger that the same XYZAppLogger is returned and that we do NOT create another XYZAppLogger. I added private static final Map loggers = new HashMap(); and modified the getLogger method (see below) to get good synchronized management of the loggers.

Now sprinkle in some isFatalLoggable(), isErrorLoggable(), etc methods (again, see below).

And one last thing... you need to set the Log4J settings. In the next section I show you how to load the properties from an external file.

The result...
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class XYZAppLogger {
	private Logger logger = null;
	
	private static final Map loggers = new HashMap();
	
	private XYZAppLogger(Class c) {
		this.logger = Logger.getLogger(c);
	}
	
	public static XYZAppLogger getLogger(Class c) {
		String className = c.getName();
		XYZAppLogger logger = (XYZAppLogger) loggers.get(className);
		if (logger == null) {
			synchronized (XYZAppLogger.class) {
				logger = (XYZAppLogger) loggers.get(className);
				if (logger == null) {
					logger = new XYZAppLogger(c);
					loggers.put(className, logger);
				}
			}
		}
		return logger;
	}
	
	public boolean isFatalLoggable() {
		return logger.isEnabledFor(Level.FATAL);
	}
	
	public boolean isErrorLoggable() {
		return logger.isEnabledFor(Level.ERROR);
	}
	
	public boolean isWarnLoggable() {
		return logger.isEnabledFor(Level.WARN);
	}
	
	public boolean isInfoLoggable() {
		return logger.isEnabledFor(Level.INFO);
	}
	
	public boolean isDebugLoggable() {
		return logger.isEnabledFor(Level.DEBUG);
	}
	
	public void fatal(String message) {
		logger.fatal(message);
	}
	
	public void fatal(String message, Throwable t) {
		logger.fatal(message, t);
	}
	
	public void error(String message) {
		logger.error(message);
	}
	
	public void error(String message, Throwable t) {
		logger.error(message, t);
	}
	
	public void warn(String message) {
		logger.warn(message);
	}
	
	public void warn(String message, Throwable t) {
		logger.warn(message, t);
	}
	
	public void info(String message) {
		logger.info(message);
	}
	
	public void debug(String message) {
		logger.debug(message);
	}
	
	private static void setUpLogger() {
		Properties properties = new Properties();
		properties.put("log4j.rootLogger", "INFO, Console");
		properties.put("log4j.appender.Console", "org.apache.log4j.ConsoleAppender");
		properties.put("log4j.appender.Console.layout", "org.apache.log4j.PatternLayout");
		properties.put("log4j.appender.Console.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");
		PropertyConfigurator.configure(properties);
	}
	
	public static void main(String[] args) {
		setUpLogger();
		
		XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
		
		Exception testException = new RuntimeException("This is a test exception");
		
		logger.fatal("Test fatal");
		logger.fatal("Test fatal", testException);
		logger.error("Test error");
		logger.error("Test error", testException);
		logger.warn("Test warn");
		logger.warn("Test warn", testException);
		logger.info("Test info");
		logger.debug("Test debug");
	}
}



Loading Log4J Configuration Files

Next I am going to show you how to load your logging configuration from an external file. We will call this file LogConfig.properties.

OK, I know what some people who are familiar with Log4J are going to say: you are supposed to call the file log4j.properties and put it in the classpath so it is loaded on startup. Well, you can do that, but I run multiple web sites that each have their own log settings. Each needs to load the settings into its own classloader separate from the others. I can't put the logging into a global logging file - it would be unmanagable. And what if you are on a shared server and have no control over the main log4j.properties file?

*steps off soap box*

Put this LogConfig.properties file into your classpath. For web apps, that would be the WEB-INF/classes directory. Here is what the contents of the file looks like:
log4j.rootLogger=INFO, Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Finally change the setUpLogger method to read this properties file:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class XYZAppLogger {
	private Logger logger = null;
	
	private static final Map loggers = new HashMap();
	
	private XYZAppLogger(Class c) {
		this.logger = Logger.getLogger(c);
	}
	
	public static XYZAppLogger getLogger(Class c) {
		String className = c.getName();
		XYZAppLogger logger = (XYZAppLogger) loggers.get(className);
		if (logger == null) {
			synchronized (XYZAppLogger.class) {
				logger = (XYZAppLogger) loggers.get(className);
				if (logger == null) {
					logger = new XYZAppLogger(c);
					loggers.put(className, logger);
				}
			}
		}
		return logger;
	}
	
	public boolean isFatalLoggable() {
		return logger.isEnabledFor(Level.FATAL);
	}
	
	public boolean isErrorLoggable() {
		return logger.isEnabledFor(Level.ERROR);
	}
	
	public boolean isWarnLoggable() {
		return logger.isEnabledFor(Level.WARN);
	}
	
	public boolean isInfoLoggable() {
		return logger.isEnabledFor(Level.INFO);
	}
	
	public boolean isDebugLoggable() {
		return logger.isEnabledFor(Level.DEBUG);
	}
	
	public void fatal(String message) {
		logger.fatal(message);
	}
	
	public void fatal(String message, Throwable t) {
		logger.fatal(message, t);
	}
	
	public void error(String message) {
		logger.error(message);
	}
	
	public void error(String message, Throwable t) {
		logger.error(message, t);
	}
	
	public void warn(String message) {
		logger.warn(message);
	}
	
	public void warn(String message, Throwable t) {
		logger.warn(message, t);
	}
	
	public void info(String message) {
		logger.info(message);
	}
	
	public void debug(String message) {
		logger.debug(message);
	}
	
	private static void setUpLogger() throws IOException {
		String path = "LogConfig.properties";
		Properties properties = new Properties();
		properties.load(XYZAppLogger.class.getClassLoader().getResourceAsStream(path));
		synchronized (XYZAppLogger.class) {
			PropertyConfigurator.configure(properties);
			loggers.clear();
		}
		XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
		logger.info("Properties loaded: " + path);
	}
	
	public static void main(String[] args) throws Exception {
		setUpLogger();
		
		XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
		
		Exception testException = new RuntimeException("This is a test exception");
		
		logger.fatal("Test fatal");
		logger.fatal("Test fatal", testException);
		logger.error("Test error");
		logger.error("Test error", testException);
		logger.warn("Test warn");
		logger.warn("Test warn", testException);
		logger.info("Test info");
		logger.debug("Test debug");
	}
}
This is evolving quite nicely...

So why Log4J when Sun has recently added java.util.logging to the core JDK? Why should you use Log4J when that requires downloading and configuring additional libraries?

First of all, a lot of open source products use Log4J so it is good to understand how it works and how to configure it. That also means there are a lot of discussions and resources out there that help you learn the best way to use it.

Secondly, I have found the java.util.logging to be less than perfect.

For example, the following code will be a problem if this class is loaded by the classloader before the logging configuration is read from your logging configuration file:
package com.mysite.test;

import XYZAppLogger;

public class MyClass {
	private static XYZAppLogger logger = XYZAppLogger.getLogger(MyClass.class);
}
Log4J handles this well. However, java.util.logging falls short. If I configure Log4J to set the com.mysite log level (a setting for everything under the com.mysite package) the MyClass logger is updated properly. But java.util.logging will NOT alter the log level for MyClass's logger because the logger has already been initialized.

The problem with java.util.logging is that, when you ask for MyClass's logger, java.util.logging creates a log configuration search chain that searches up the package and puts that search chain into the newly created logger. It will first check com.mysite.test.MyClass, then com.mysite.test, then com.mysite, then com and finally the root log settings. The first log settings it finds it uses. The problem is, in order to use less memory, if there is no log configuration for com.mysite when the logger is obtained (before the logging properties are loaded) the com.mysite is not included in the search chain. So when the log configuration is loaded, your logger has already been created and it is missing the com.mysite check point so those settings are skipped. Let me tell you, it took days of digging through source code to get to the bottom of this bug. Of course I found this in JDK 1.4 and never checked it in JDK 1.5. It may be fixed, if you try it out, I would love to know.

All in all, I hope you spread the word about logging. I believe, no matter what level a programmer is at, there is no excuse for using System.out or System.err for their logging. Frankly, unless you are writing a console application, I would recommend acting like System.out and System.err don't even exist.

MORE >>
Posted by Aaron Bono at 6/19/2006 8:36 PM | View Comments (0) | Add Comment | Trackbacks (0)