Best Practices For Automation Tester to Avoid Java Memory Leak Issue
Last Updated :
24 Apr, 2025
A memory leak is a type of issue that will not cause any problem unless Java Heap Memory is overflown, but once you start getting this issue during execution, it’s very difficult to find out the root cause of it. So, it’s better to prevent memory leak issues in code from the very beginning. While building a test automation framework using Java, we should be considering many best practices but, in this article, we would be discussing three important best practices that we should keep in mind.
Practice No. 1: Reading Excel Files, Properties Files, and Managing Database Connection
Reading an excel file is very common in a test automation framework, either it could be to store test data, credentials, configuration, etc. Apache POI is the external jar that we mostly use to read an excel file. Let’s see the java code to write an XLSX file where we are trying to set the header of the excel file. We have created objects of XSSFWorkbook class and FileOutputStream class at the very beginning with global scope. The object workbook is then used to initialize with a new XSSFWorkbook instance followed by creating the sheet and row and setting the cell value. Whereas outputStream object is initialized with FileOutputStream class by accepting the excel file path as an argument. Important to notice that once the work is done, both the workbook and outputStream object is closed to avoid any memory leak. Also, note that these objects are even close in the catch section, so that in case any exception occurs in between, the memory leak can be avoided then as well. The same is applied to the Properties file as well.
Java
public static void writeExcelData(String filePath)
throws IOException
{
XSSFWorkbook workbook = null ;
FileOutputStream outputStream = null ;
try {
workbook = new XSSFWorkbook();
XSSFSheet sheet
= workbook.createSheet( "Stock Analysis" );
Row row = sheet.createRow( 0 );
row.createCell( 0 ).setCellValue( "Stock Name" );
row.createCell( 1 ).setCellValue( "Current Index" );
row.createCell( 2 ).setCellValue( "Today's Change" );
row.createCell( 3 ).setCellValue( "52 Week High" );
row.createCell( 4 ).setCellValue( "52 Week Low" );
row.createCell( 5 ).setCellValue(
"High Minus Current Index" );
row.createCell( 6 ).setCellValue(
"Current Index Minus Low Index" );
outputStream = new FileOutputStream(filePath);
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
catch (Exception e) {
System.out.println( "Exception occurred : " + e);
if (workbook != null ) {
workbook.close();
}
if (outputStream != null ) {
outputStream.close();
}
}
}
|
It’s also very common to set up a connection with Database in a test automation framework, either for database testing or to fetch or store data in the database. Let’s take an example to connect to Maria DB. We have globally declared two objects conn and stmt of class Connection and Statement respectively. Note how we have closed the conn and stmt once the work is done. In case of exception, finally block is always executed and we have closed conn and stmt here as well.
Java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Mariadb {
static final String JDBC_DRIVER
= "org.mariadb.jdbc.Driver" ;
static final String DB_URL
static final String USER = "root" ;
static final String PASS = "root" ;
public static void main(String[] args)
{
Connection conn = null ;
Statement stmt = null ;
try {
Class.forName( "org.mariadb.jdbc.Driver" );
System.out.println(
"Connecting to a selected database..." );
conn = DriverManager.getConnection(
"root" );
System.out.println(
"Connected database successfully..." );
System.out.println(
"Creating table in given database..." );
stmt = conn.createStatement();
String sql
= "INSERT INTO CUSTOMER VALUES('FIRST NAME','SECOND NAME')" ;
stmt.executeQuery(sql);
stmt.close();
conn.close();
}
catch (SQLException se) {
se.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
if (stmt != null ) {
stmt.close();
conn.close();
}
}
catch (Exception se) {
}
}
}
}
|
One of the easiest approaches is to enable memory leak management in the IDE you are using. For Example, you can do the below setup in Eclipse. If you do so, Eclipse will automatically give you a compilation error in case any memory leak occurs in the code.
Navigate to Window – Preferences – Java – Compiler – Errors/Warnings – Potential Programming Problems – Change the settings of “Resource leak” and “Potential resource leak” to “Error” instead of “Warning”. Click on Apply and Ok. You will notice Eclipse automatically throws a compilation error for the memory leak.

IDE Settings in Eclipse to prevent memory leak
Practice No. 2: Use Singleton Design Pattern
Java provides a variety of design patterns to use in different cases. But we would be talking about Singleton Design Pattern today which can be very useful in a test automation framework. As mentioned earlier, we need to read excel files, and properties files, and establish a database connection, very often in the framework. Let’s assume that we need to read the same excel or properties file in our code but at different places. It’s very inefficient if we create different objects to access the same file at various places in our code.
Using Singleton Design Pattern, we can restrict the creation of an object of a class from outside of the class by making the constructor private. That way the object can be created only from inside the class and it returns the same object whenever called. Hence, Singleton Design Pattern can be used to restrict unnecessary object creation to read various files and establish a database connection in our test automation framework.
Let’s see an example of how to create a Singleton class to establish a Maria DB Connection. In the below example, the constructor MariaDBConnectionSingleton constructor has been made private and within which the code to establish a connection is written. As the constructor is private, it can’t be called from outside of the class. First, we need to call the getInstance() static method to get an object of the class MariaDBConnectionSingleton and then call the getConnection() method using that object to get the conn object. Note that in this way always the same conn object is being returned and no additional Connection object is being created.
Java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MariaDBConnectionSingleton {
private static MariaDBConnectionSingleton instance;
private Connection conn;
private String userName = "root" ;
private String password = "root" ;
private String schemaName = "bdd_framework" ;
private String mariaDBConnectionString
private MariaDBConnectionSingleton() throws SQLException
{
try {
this .conn = DriverManager.getConnection(
mariaDBConnectionString + "/" + schemaName,
userName, password);
}
catch (Exception ex) {
System.out.println(
"Something is wrong with the DB connection String : "
+ ex.getMessage());
}
}
public Connection getConnection() { return conn; }
public static MariaDBConnectionSingleton getInstance()
throws SQLException
{
if (instance == null ) {
instance = new MariaDBConnectionSingleton();
}
else if (instance.getConnection().isClosed()) {
instance = new MariaDBConnectionSingleton();
}
return instance;
}
}
|
Practice No. 3: Initialize the initial capacity of ArrayList and HashMap if Possible
Java Collection is a big world itself but we may not need to discuss everything here. As an automation testers, we use ArrayList and HashMap very often. Let’s discuss ArrayList and HashMap.
ArrayList: ArrayList gives us the flexibility of storing a similar type of data like an Array, the only difference is in Array we can’t dynamically expand the size of the Array if needed whereas in ArrayList the size can be expanded dynamically. But if we understand the internal functioning of ArrayList, we will see that it’s actually backed up by an Array in the background. What happens is, when we declare an ArrayList, in the background, it creates an Array of size 10 (by default size). Java uses a factor called Load Factor (which is by default 0.75), which determines when to grow the initial array (which was of size 10). Total Capacity * Load Factor – which is 10 * 0.75 = 7, that means when the 7th element in the initial Array is inserted, the Array would double its initial capacity, i.e. of size 20.
Now the question is how it increases the capacity. Well in the background Java actually creates another array of size 20 and copies the 7 elements from the initial array, here is when it takes O(n) time where n is the number of elements. We should always try to avoid this O(n) time as it may cause a Java Heap Memory issue. Think about a situation if need to deal with many elements and copy the elements to a new dynamically increased ArrayList again and again.
We can avoid it if we know what would be the maximum size of the ArrayList before ahead and declare the ArrayList with the initial capacity. Let’s take an example, assume that we have a requirement to store all the JSON file names in an ArrayList<String>. Below is the code we should write.
Java
public static List<String>
getJSONFileNames(String folderPath)
{
try {
File folder = new File(folderPath);
File[] listOfFiles = folder.listFiles();
int totalFiles = 0 ;
if (listOfFiles != null ) {
totalFiles = listOfFiles.length;
}
int initialCapacity = ( int )(totalFiles / 0.75 ) + 1 ;
List<String> jsonFileName
= new ArrayList<>(initialCapacity);
for (File file : listOfFiles) {
if (file.isFile()) {
if (file.getName().trim().endsWith(
"json" )) {
jsonFileName.add(folderPath + "\\"
+ file.getName());
}
}
}
return jsonFileName;
}
catch (Exception e) {
return new ArrayList<>();
}
}
|
In the above example, we first got the totalFiles of the folder. Please note that we have used a formula (expected size/load factor) +1 to calculate the initial size of the ArrayList. As explained earlier, Java automatically allocates another array with double the size of the initial size as soon as 75% (Load Factor 0.75) of the array is filled. Hence to avoid it, we should initialize the ArrayList with more than 25% of the total initial capacity (i.e. 15 if the expected initial size is 10).
HashMap: HashMap is another popular Java Collection that we use very often in our test automation framework. The main advantage is its Key-Value pair nature. For example, if we want to read data from a database, we can put the data in a key pair value combination where the key should be the column name. Similarly, reading data from excel and storing it in HashMap where the column name should be a key value.
Likewise, ArrayList and HashMap also have a similar concept of Load Factor and Initial Capacity where 0.75 is the default load factor and 16 is the default initial capacity of a HashMap. In HashMap, first, a hashcode is generated, and accordingly, the element is placed. It’s possible that the same hashcode is being generated for multiple elements, and that is when a collision happens. In such cases, a LinkedList is formed to store the element with which collision occurs. Inserting and retrieving data from HashMap becomes slow if we encounter such collisions. The collision can be avoided if we follow the same rule that was explained in ArrayList, i.e. to declare the initial capacity of HashMap using the formula (expected size/load factor) +1.
Similar Reads
First Step in Evaluating Java Performance Issue
Among all the programming languages used around the world, Java is one of the most widely used. It is known for its versatility and ability to run on a variety of platforms, making it an ideal choice for a wide range of applications. However, like any other programming language, Java can experience
6 min read
How to Use Jenkins For Test Automation?
In the current dynamic software development arena, automation is paramount to the realization of quality and efficiency. These could be assured with test automation, greatly applied under the standard practices of CI and CD. Jenkins is used in implementing CI/CD pipelines. In this article, we will s
6 min read
How to Rerun the Failures in a BDD Junit Test Automation Framework?
We come across failures many times after we execute our test scenarios through automation. These failures can be because of majorly two reasons: Functional Issue (actual failure).Intermittent Issues (Application Slowness, Page not loaded properly, Technical Issue, etc.). Having a mechanism to automa
7 min read
Test Tools and Automation- Strategies , Costs, Risks and Benefits
Test tools can be very useful. Some tools are essential. For example- An incident tracking system. Generally, tools are meant to improve the efficiency and accuracy of testing. Hence, it is very important that we carefully select tools and properly implement them as well. Many times, it has been obs
7 min read
How to pass java code a parameter from Maven for testing?
Maven is one of the most commonly used tools in Java projects for build automation, allowing the developer to manage the dependency of their project and many other operations, like compiling codes or running tests. Sometimes, you may want to pass some parameters from Maven to the Java code, so you c
3 min read
How Long Would It Take to Learn Automation Testing?
Automation Testing is the fourth important step in the Software Development Life Cycle. Before sending any product to production, it is crucial to run manual and automation tests on the product to ensure its efficiency, accuracy, and reliability, so as not to experience any failure at the time of pr
10 min read
How to find max memory, free memory and total memory in Java?
Although Java provides automatic garbage collection, sometimes you will want to know how large the object heap is and how much of it is left. This information can be used to check the efficiency of code and to check approximately how many more objects of a certain type can be instantiated. To obtain
3 min read
How to Start Automation Testing from Scratch?
Automation Testing is the practice of using automated tools and scripts to execute tests on software applications, reducing manual effort and increasing efficiency. Starting automation testing from scratch involves several key steps, including selecting the right automation tool, identifying test ca
8 min read
What is Low Code Automation Testing?
Low-code test automation is a way of automating software tests without needing a lot of coding knowledge. It uses tools that offer easy-to-use visual interfaces, drag-and-drop features, and ready-made components, so even people with little coding experience can create and run automated tests. This m
11 min read
Low-Code vs. No-Code Test Automation
Low-code and no-code test automation are transforming the way teams approach software testing. These platforms simplify the process, enabling both technical and non-technical users to automate tests quickly and efficiently. In this article, weâll explore the key differences between low-code and no-c
8 min read