Fri. Apr 19th, 2024

Over the years I have done a number of code reviews there have been a number of common mistakes that I have found. So here are my Java tips a series of posts going over these tips.

Tip 1: Throw original Exception

Many times in our code we will catch an exception and we may want to output that exception to our logs, with additional information that we have only at this spot in the code, or to wrap it in a custom exception that may get handled higher up the stack differently. In either case, we never want to just drop the exception we caught. It provides too much valuable information in debugging.

So if we have code like this:

public void validatePort(String input) throws NumberFormatException {
	try {
		// do something
	} catch (NumberFormatException e) {
		log.error("Error validating port: {}", input, e);
                throw e;
	}
}

In this code, we will print out the relevant information needed to see what the string was that failed. The exception will be logged, with that information. We throw the exception on up the stack, for possible handling above.

Now let’s say we want to create a special exception to handle this scenario up the stack in a less generic way. We can do this by creating our own custom exception. However, we still shouldn’t lose that original exception.

Let’s say we have this code:

class PortFormatException extends Exception {
        ErrorCode errorCode;

        public PortFormatException(String message, Throwable cause, ErrorCode errorCode) {
            super(message, cause);
            this.errorCode = errorCode;
        }
    }

This is our try/catch block:

public void validatePort(String input) throws PortFormatException {
	try {
		// do something
	} catch (NumberFormatException e) {
		throw new PortFormatException("Port format should be 1001-9999", e, ErrorCode.INVALID_PORT_CONFIGURATION);
	}
}

This code will throw a specific exception indicating that the format of the port string was incorrect. Your application can take appropriate measures for this. However, for debugging purposes passing the “e” into the exception, and then into the superclass as the cause, you will maintain the exact exception and all relevant information that occurred.

Tip 2: Check for NULL on lists

Too often I see code that is like this:

        List<String> list = doSomethingReturnList();
        Optional<String> any = list.stream().filter(item -> item.contains("BOB")).findAny();

if the doSomethingReturnList() returns a null because there was no list to return, you will get a NullPointerException. Nobody wants that. Yes, you say we have an agreement that doSomethingRetunrList() will always return a list even if empty. However, when working with third-party libraries you do not have that same ability. So I wrote a little ListUtil method I like to use ListUtils.safe(). This is a fast and efficient way to make sure those lists are safe to use. like this:

	List<String> list = ListUtil.safe(doSomethingReturnList());
        Optional<String> any = list.stream().filter(item -> item.contains("BOB")).findAny();

Now you are guaranteed to be safe and avoid those NPE’s.

Tip 3: Parallel Stream thread safety

If you use parallel streams to process a stream, you must make sure your code is thread-safe. Take the following code:

        StringBuilder builder = new StringBuilder();
	List<String> list = ListUtil.safe(doSomethingReturnList());
        list.parallelStream().filter(item -> !item.contains("BOB")).forEach(item -> builder.append(item));

Now at first glance, this may appear to be fine, however, StringBuilder is not thread-safe. Any code you use in this stream must be thread-safe or you can get unexpected results. In this particular case you want to use StringBuffer which is thread-safe.

        StringBuffer buffer = new StringBuffer();
	List<String> list = ListUtil.safe(doSomethingReturnList());
        list.parallelStream().filter(item -> !item.contains("BOB")).forEach(item -> buffer.append(item));

Switching code from stream() to parallelStream() requires checking the code to make sure everything is thread-safe.

Tip 4: Logging with Formatting

If your logging is SLF4J then make sure you make use of formatting in the logging calls. Personally, I won’t use anything but SLF4J after seeing improvements with this formatting. Typical logging code will look something like this:

        List<String> list = ListUtil.safe(doSomethingReturnList());
        list.parallelStream().filter(item -> !item.contains("BOB")).forEach(item -> logger.debug("Item: " + item + " w/o Bob" ));

The problem with this code is in the logger.debug(“Item: ” + item + ” w/o Bob” ). This will be processed as follows:

  1. “Item: ” + item will be combined to make a new String A.
  2. String A and “w/o Bob” will be combined to make new String B.
  3. String B will be passed to debug and the logger is not printing debug prints currently.

Two new objects are created and the processing is necessary to create them every before we know if we need to do that. Instead, do this:

         list.parallelStream().filter(item -> !item.contains("BOB")).forEach(item -> logger.debug("Item: {} w/o Bob",item ));

Now everything is passed into the debug() method and it can decide if additional processing to format the strings are necessary. No new objects were created and no additional processing, improving speed, memory usage, and garbage cleanup.

Tip 5: Logging include the exception

It shocks me how often an exception is not passed into logging to be recorded. I will often find code like this:

public void validatePort(String input) throws NumberFormatException {
	try {
		// do something
	} catch (NumberFormatException e) {
		log.error("Error validating port: {}", input);
                throw e;
	}
}

The problem with this is you lose valuable information. You are not logging anything about the exception. Again information is lost when your trying to debug something. Instead, slf4j will accept as its last parameter a throwable. Which it will output the message and stack trace to the logs automatically with this information. Instead just pass in that throwable:



public void validatePort(String input) throws NumberFormatException {
	try {
		// do something
	} catch (NumberFormatException e) {
		log.error("Error validating port: {}", input, e);
                throw e;
	}
}

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.