You are here
Home > java >

Java Coding Best Practices and Standards

Java Coding Best Practices and StandardsAs a developer, while doing coding task, there is a probability that we make some mistakes accidentally. Sometimes, the compiler catches the mistakes and warns us to correct it. But when we make a mistake and compiler didn’t catch it, we may face an issue after running the program. That’s why we should have some elementary knowledge of Java Coding Best Practices and Standards in order to avoid them. These Java Coding Best Practices and Standards are not only useful in writing a successful program, but also in improving the performance of the application in the long term as a whole.

Based on the Code Review, we are going to mention a synopsis of the common mistakes that a Programmer commits. Therefore, we will learn it in the reverse way, such as what are the mistakes we generally do while writing the codes and how to improve them accordingly. Let’s start discussing the topic ‘Java Coding Best Practices and Standards’.

Why to follow the Java Coding Best Practices and Standards?

The Java Coding Best Practices and Standards are essential to programmers for a number of reasons.

1) It is almost impossible to maintain a software for its whole life by the original author. Hence, Java Coding Best Practices and Standards help other developers (who are not the author of the code) to understand the code easily.
2) Code conventions and Standards also improve the readability of the software. Consequently, Java Coding Best Practices and Standards allow java developers to understand new code faster and effortlessly.
3) Sometimes we ship our source code as a product. Java Coding Best Practices and Standards help in making sure that our software is well packaged and clean as any other product.

Common Mistakes

Here are some of the most common mistakes that a programmer does while coding. The ‘Java Coding Best Practices and Standards’ suggest us to pay an additional attention on these points while coding.

1) Neglecting the NullPointerException
2) Instantiating Objects Inside Loops
3) Using the ‘new’ keyword while creating String
4) Forgetting to free Resources after use
5) Writing Code that causes Memory Leaks
6) Unreasonable Garbage Allocation
7) Bypassing Exceptions
8) Returning Null Instead of Empty Collections
9) Overlooking ConcurrentModificationException
10) Missing the ‘break’ Keyword in a Switch-Case Block
11) Overlooking Existing Libraries
12) Incorrect way to compare Objects
13) Ignoring usage of Generics
14) Unnecessary usage of StringBuffer
15) Incorrect use of common naming conventions & comments

Neglecting NullPointerException 

Many people in the industry believe that you are not a real Java programmer until you have not dealt with a NullPointerException. Since NullPointerException indicates the absence of a value, the null reference is the source of many problems. From the various code reviews, It is observed that programmers tend to neglect the more common & popular NullPointerException. If there is any object that can be a possible candidate of being null, we should always apply a null check. Even we can use the ‘Optional’ introduced in Java 8.

Sometimes programmers either miss to apply null check or don’t apply it in a correct way. While coding we should take an extra care for NullPointerException. Point out the places where it may happen, list those places in your //TODO list to fix them later so that this exception may not occur at run-time. In this way, we can also improve our code by following Java Coding Best Practices and Standards.

Unsuitable way of checking null reference

Consider the below piece of code:

private boolean isNew(String action) {
 if (action.equalsIgnoreCase("New")) { 
     return true; 
     } else { 
     return false;
     } 
}

Here, in method isNew(), if by mistake, we pass the value of “action” variable as null it will raise a NullPointerException.

So, we should change our code as below to avoid Null Pointer Exception

if ("new".equalsIgnoreCase(action)) {
   ...
}

Checking of Null Values (Applying Null Check)

Consider the below piece of code :

String employeeId = getEmployee("A015"); 
if (employeeId != null) { 
       System.out.println(employeeId.toString()); 
   } else { 
       throw new RuntimeException("Employee does not exist");
}

In the code snippet above, if we don’t apply null check for employeeId, it may cause a NullPointerException as we are getting employeeId from a different method.

Avoid returning null values from empty Collections

An empty collection is collection which hasn’t any elements. Some developers assign null value for Collection, which has no elements, but this is a bad practice. Instead of assigning null, We should use Collections.EMPTY_LIST, Collections.EMPTY_SET and Collections.EMPTY_MAP etc. Consider the below peice of code:

public static List<Location> getLocations() { 
   List<Location> listOfLocations = null; // (incorrect way) 
   List<Location> listOfLocations = Collections.EMPTY_LIST; // (correct way) ​
   ........ // logic to get List of Locations  
   return listOfLocations; 
} 

Use methods of StringUtils class

StringUtils  class is part of org.apache.commons.lang package, We can use it to avoid NPE specially all its methods are null safe. For example, StringUtils.IsEmplty(), StringUtils.isBlank(), StringUtils.equals() etc and more.

Use ‘Optional’ of Java 8

It is also one of the most interesting feature among all new features in Java 8. Optional is a new class under package java.util. The purpose of this call is to address the NullPointerException. Optional class can address some of these problems.

Instantiating Objects Inside Loops

It’s not a good practice to instantiate an object inside a loop. If we do this, we are creating as many objects as the number of iterations in the loop. Here’s an example of code :

public class MyClass { 
   public static void main( String args[] ) { 
     for (int i = 0; i <= 10; i++) {
       Foo f = new Foo(); //Avoid this whenever you can.
       f.getMethod(); 
     }
   } 
}

Using the ‘new’ keyword while creating String

String s1 = new String("AnyString") ;  //slow instantiation

The constructor always creates a new object, and adds the literal to the heap. while

String s2 = "AnyString" ;    //fast instantiation

This shortcut refers to the item in the String pool and creates a new object only if the literal is not in the String pool.

Forgetting to free Resources after use

While reviewing the code, It has been observed that we forget to free the resources after use such as file, I/O streams, database connections, and sockets, etc. Every time a program opens a resource, it should be an important practice to release them after use.

Java 7 has introduced a special statement try-with-resource that we can use proactively to manage it. We can use this statement on any object that implements the AutoClosable interface. It ensures that each resource is closed by the end of the statement. In this way, we can achieve Java Coding Best Practices and Standards.

Writing Code that causes Memory Leaks

However, Java uses automatic memory management. It doesn’t mean that a developer should not have an idea about how memory utilization happens in the application. There are still chances of issues with memory allocations. As long as a program creates references to objects that are not needed anymore, it will not be freed. We can still call this a memory leak.

In Java, Memory Leaks can take place in several ways. However, the most common reason is everlasting object references. Since the garbage collector can’t remove objects from the heap if they still have references. One can create such a reference by defining a class with a static field containing some collection of objects, and forgetting to set that static field to null after the collection is no longer needed. Static fields are considered GC roots and are never collected.

Another potential reason behind such memory leaks is a circular dependency. A group of objects referencing each other, can cause circular dependencies. In that case, the garbage collector is unable to decide whether these objects with cross-dependency references are required or not.

To prevent memory leaks, it is important to ensure that objects are properly released and garbage collected when they are no longer needed, and to use good coding practices such as avoiding unnecessary object creation, properly closing resources, and using weak references when appropriate.

Unreasonable Garbage Allocation

We may even face the issue of excessive Garbage Allocation when a program generates a lot of short-term objects. In that case the Garbage Collector has to work constantly to remove unnecessary objects from the memory. In fact, it may become a concern of the performance of the application that can also affect the performance negatively. For example, if we have a program that creates a String in a loop after each iteration. Since strings are immutable. So, on each iteration a new string will be created. In order to deal with this, we should use a mutable StringBuilder instead. In this way, we can achieve Java Coding Best Practices and Standards.

Bypassing Exceptions

It is always one of the best practice to handle exceptions in the code where it is possible. Exceptions are thrown on a specific purpose, so in most cases we should address the issues causing these exceptions. If necessary, you can either re-throw it, show an error dialog to the user, or add a message to the log. At least, we should explain the reason of an unhandled exception in order to let other developers find out the reason. Developers sometimes catch exceptions, but don’t handle them properly, resulting in runtime errors or unexpected program behavior.

Some developers use broader exception handlers that catch all exceptions, which can hide errors and make debugging difficult. It’s better to use specific exception handlers and handle exceptions properly.

Returning Null Instead of Empty Collections

It is a good practice to return an empty collection instead of null. It will obviously help avoid a NullPointerException. Let’s consider the following method that traverses a collection obtained from another method, as shown below:

List<String> invoiceNumbers = invoiceRepository.getInvoiceNumbers();
for (String invoiceNumber : invoiceNumbers) {
    processInvoice(invoiceNumber);
}

In the example above, if getInvoiceNumbers returns null due to any reason, then it will raise a NullPointerException. However, if it returns an empty list instead of returning a null, then NullPointerException is no longer a problem. Moreover, the code is cleaner since we don’t need to null-check the variable invoiceNumbers.

In fact, We can even apply a shorter solution, if we use Java 8 and later version of JDK. Here, we can use Optional type that can either be an empty object or a wrap of some value as below:

Optional<String> optional = Optional.ofNullable(nullableString);
if(optional.isPresent()) {
    System.out.println(optional.get());
}

or even
Optional<String> optional = Optional.ofNullable(nullableString);
optional.ifPresent(System.out::println);

Overlooking ConcurrentModificationException

When we iterate over a collection using methods other than those provided by the iterator object, We can encounter a ConcurrentModificationException if modify the collection while iterating. For example, While iterating over a List of Invoices we want to remove the invoices which have amount greater than or equal to 100.

List<Invoice> invoices = new ArrayList<>();
invoices.add(new Invoice("Inv1",98.5)); 
invoices.add(new Invoice("Inv2",118.75)); // Candidate for removal
invoices.add(new Invoice("Inv3",93.25));
for (Invoice invoice : invoices) {
    if (invoice.getInvoiceAmount() >= 100.0) {
        invoices.remove(invoice);
    }
}

If we run this code, we will encounter a ConcurrentModificationException, because the code modifies the List while iterating it.

Moreover, the same exception may arise if one of the multiple threads working with the same list and trying to modify the collection while others iterate over it. In that case, we should also think about the synchronization locks or some of the collections that deal with concurrent modification.

Solution#1

We can collect all objects which are candidate for removal in a separate collection. Then remove them from another loop. But this method requires one additional collection for collecting the items.

List<Invoice> invoicesForRemoval = new LinkedList<>();
for (Invoice invoice : invoices) {
    if (invoice.getInvoiceAmount() >= 100.0) {
        invoicesForRemoval.add(invoice);
    }
}
for (Invoice invoice : invoicesForRemoval) {
    invoices.remove(invoice);
}

Solution#2

We can use iterator() method of the collection to make it more concise as below:

Iterator<Invoice> invoiceIterator = invoices.iterator();
while (invoiceIterator.hasNext()) {
    Invoice invoice = invoiceIterator.next();
    if (invoice.getInvoiceAmount() >= 100.0) {
        invoiceIterator.remove();
    }
}

Solution#3

We can even use ListIterator here as it applies specifically to List Collection. Iterators that implement ListIterator interface support not only removal operations, but also add and set operations.

ListIterator<Invoice> inviceIterator = invoices.listIterator();
while (inviceIterator.hasNext()) {
    Invoice invoice = invoiceIterator.next();
    if (invoice.getInvoiceAmount() >= 100.0) {
        invoiceIterator.remove();
    }
}

Solution#4

Another way is to use stream methods introduced in Java 8. We can convert a collection into a stream and filter that stream according to our criteria. Here is an example of how stream api could help us filter invoices and avoid “ConcurrentModificationException”.

invoices = invoices.stream().filter((invoice -> invoice.getInvoiceAmount() < 100.0))
        .collect(Collectors.toCollection(ArrayList::new));

Solution#5

Even the most concise way is to use List.removeIf method presented in Java 8 as below.

invoices.removeIf(Invoice::isInvoiceAmountGTEHundred);

Here, isInvoiceAmountGTEHundred is a method to check if Invoice amount is greater than or equal to 100.

Solution#6

In order to avoid a possibility of ConcurrentModificationException, we should use ‘CopyOnWriteArrayList’ instead of an ArrayList. Since  ‘CopyOnWriteArrayList’ offers modification methods such as set, add, and remove that don’t change the backing array of the collection. Rather it creates a new modified version of it. It allows iteration over the original version of the collection and modifications on it at the same time, without a risk of “ConcurrentModificationException”. However, there is a conclusive drawback of this collection. It generates a new collection with each modification. Moreover, we also have such type of collections for Set & Map which are ‘CopyOnWriteSet’ and ‘ConcurrentHashMap’ respectively.

Missing the ‘break’ Keyword in a Switch-Case Block

It is a very common mistake to miss break statement in switch-case block. For example, consider the below code


    	int index = 0;
    	switch (index) {
        	case 0:
            	System.out.println("Sunday");
        	case 1:
            	System.out.println("Monday");
            	break;
        	case 2:
            	System.out.println("Tuesday");
            	break;
                case 3:
                System.out.println("Wednesday");
            	break;
                case 4:
                System.out.println("Thursday");
            	break;
                case 5:
                System.out.println("Friday");
            	break;
                case 6:
                System.out.println("Saturday");
            	break;
        	default:
            	System.out.println("Not a Day Of Week");
    	}

If we missed to write a “break” in “case 0” in the code example above, the program will print “Sunday” followed by “Monday”, since the control flow inside here will go through the entire “switch” statement until it reaches a “break”.

Overlooking Existing Libraries

Many a times, while doing code reviews It is observed that programmers ignore the existing libraries that can solve their purpose without writing an extra code to implement a functionality. There are multiple reasons behind it. Sometimes it happens because of no knowledge of the existing library. A better way is to just check on the internet before the implementation if there is an existing library to get the functionality implemented.

For example, while applying try-catch-finally in a program, we generally use finally to release resources that we have used in traditional try-catch statement. We can avoid releasing resources, if we use try-with-resource, introduced in JDK 7. Similarly, we can implement many functionalities without writing an extra code, if we use the newer versions of JDK. By doing so, we can reach Java Coding Best Practices and Standards.

Incorrect way to compare Objects

It has been observed from the code review that many developers incorrectly use the operator ‘==’ and equals() method. The ‘==’ operator compares if object references or equal or not. The equals() method compares the content of Objects. In most of the cases, we use the equals() method to compare two objects. Please have an extra care in using these two ways of comparing strings as we may get unexpected results if we don’t know the differences between them.

Ignoring usage of Generics

We should always use the Generics to avoid any type of errors from raw types. Doing so, the compiler will detect any inconsistency right away and further minimize the chances to break the type safety.

Unnecessary usage of StringBuffer

When it comes to using other library instead of String as String can create too many objects, we tend to use StringBuffer without having any pros & cons in the mind. In this scenario, we should also think about StringBuilder instead of StringBuffer. Since StringBuffer is synchronized in nature, it can create a lot of overhead. When synchronization is not the priority, we can simply use StringBuilder.

Using static methods excessively

Some developers sometimes use static methods excessively, which can lead to issues with concurrent access and poor code design. It’s better to use instance methods unless there is a specific reason to use a static method.

Incorrect use of common naming conventions & comments

Java developers sometimes fail to follow coding conventions such as using proper indentation, naming conventions, and comments. This can make the code difficult to read and maintain. Below are some common conventions that are important to discuss under the topic ‘Java Coding Best Practices and Standards’.

Java Source Files

Each Java source file contains a single public class or interface. When private classes and interfaces are associated with a public class, you can put them in the same source file as the public class. The public class should be the first class or interface in the file.

Beginning Comments

All source files should begin with a c-style comment that lists the author(s), the version,  and also a brief description of the purpose of the program. For example:

/*
* Purpose Of the class
* 
* Author
* 
* Version info
*/

Package and Import Statements

The first non-comment line of most Java source files is a package statement. After that, import statements can follow. For example:
package java.util;
import java.util.ArrayList;

Class and Interface Declarations

After Package declaration, the next turn is of Class and Interface Declarations. The first letter of the name of a class or interface should be in uppercase. If a class of interface has any annotation we need to apply the same before the Declaration of Class or Interface.

@FunctionalInterface   
interface MyInterface {

    void m1();
}

Class (static) Variables

The standard order of static variables is : first the public class variables, then the protected, and then the private.

Instance Variables

The standard order of instance variables is : first the public, then protected, and then private.

Constructors

In case of parameterized constructors, the constructors with lesser number of fields should come first.

Methods

The methods should be grouped by functionality rather than by scope or accessibility. For example, a private class method can be in between two public instance methods. The goal is to make reading and understanding the code easier.

Comments

Comments are one of the most important items under the list of Java Coding Best Practices and Standards.

We should use Comments to give overviews of code and provide additional information that is not readily available in the code itself. Moreover, Comments should contain only information that is relevant to reading and understanding the program.

Java programs can have two kinds of comments: implementation comments and documentation comments. Implementation comments are delimited by /*…   */, and //. Documentation comments (known as “doc comments”) are Java-only, and are delimited by /**…*/. Doc comments can be extracted to HTML files using the javadoc tool.

Implementation comments are for commenting out code or for comments about the particular implementation. Doc comments are to describe the specification of the code, from an implementation-free perspective, to be read by developers who might not necessarily have the source code at hand. The frequency of comments sometimes reflects poor quality of code. When you feel compulsory to add a comment, consider rewriting the code to make it clearer.

Some More…

The list is even not complete. These are also essential to list them under ‘Java Coding Best Practices and Standards’. Here are some more to take care of:

1) Usage of global variable excessively: Developers sometimes use global variables too much, which can result in poor code design and issues with concurrent access. It’s better to use local variables or instance variables unless there is an explicit reason to use a global.

2) Usage of static methods excessively: Developers sometimes use static methods too much, which can also lead to issues with concurrent access and poor code design. It’s better to use instance methods unless there is a specific reason to use a static method.

3) Improper synchronization: Developers sometimes fail to properly synchronize access to shared resources, which can lead to race conditions and other concurrency issues. It’s essential to use synchronization correctly in order to avoid these problems.

4) Hardcoding values: Developers sometimes hardcode values such as configuration parameters, which can make the code inflexible and difficult to maintain. It’s better to use constants or configuration files instead.

5) Inefficient string concatenation: Developers sometimes use the ‘+’ operator to concatenate strings, which can be inefficient for large numbers of strings. Instead, we should use StringBuilder or StringBuffer as needed.

6) Improper use of collections: Developers sometimes use collections inadequately, which can lead to poor performance or memory issues. It’s essential to use the appropriate collection for the particular use case.

7) Neglecting code readability: Developers sometimes neglect to format their code properly, leading to code that is difficult to read and maintain. It’s important to use proper formatting and naming conventions to make the code readability better.


Java Coding Best Practices and Standards

 

 

 

 

 

 

Leave a Reply


Top