In today’s fast-paced development world, debugging can easily become a dreaded task, so here is the complete guide to Debugging Java code in IntelliJ. You write what seems like perfect code, only to watch it fail mysteriously during runtime. Furthermore, maybe a NullPointerException crashes your app at the worst moment, or a complex bug hides in tangled logic, causing hours of frustration. Even with AI-powered coding assistants helping generate boilerplate, the need to understand and troubleshoot your code deeply has never been greater, especially when debugging Java code in IntelliJ.
For example, imagine spending a whole afternoon chasing an elusive bug that breaks customer workflows—only to realize it was a simple off-by-one error or a condition you never tested. This experience is all too real for developers, and mastering your debugging tools can mean the difference between headaches and smooth sailing when debugging Java code in IntelliJ.
That’s where IntelliJ IDEA’s powerful debugger steps in — it lets you pause execution, inspect variables, explore call stacks, and follow exactly what’s going wrong step by step. Whether you’re investigating a tricky edge case or validating AI-generated code, sharpening your IntelliJ debugging skills transforms guesswork into confidence.
This post will guide you through practical, hands-on tips to debug Java effectively with IntelliJ, ultimately turning one of the most daunting parts of development into your secret weapon for quality, speed, and sanity.
Why do we debug code?
When code behaves unexpectedly, running it isn’t enough — you need to inspect what’s happening at runtime. Debugging lets you:
Pause execution at a chosen line and then inspect variables.
Examine call stacks and then jump into functions.
Evaluate expressions on the fly and then change values.
Reproduce tricky bugs (race conditions, exceptions, bad input) with minimal trial-and-error.
Additionally, good debugging saves time and reduces guesswork. Moreover, it complements logging and tests: use logs for high-level tracing and debugging Java code in IntelliJ for interactive investigation.
Prerequisites for Debugging Java code in IntelliJ
IntelliJ IDEA (Community or Ultimate). Screenshots and shortcuts below assume a modern IntelliJ release.
JDK installed (e.g., Java 21 or whichever version your project targets).
A runnable Java project in IntelliJ (Maven/Gradle or a simple Java application).
Key debugger features and how to use them
1. Breakpoints
A breakpoint stops program execution at a particular line so you can inspect the state.
How to add a breakpoint: Click the gutter (left margin) next to a line number or press the toggle shortcut. The red dot indicates a breakpoint.
Breakpoint variants:
Simple breakpoint: pause at a line.
Conditional breakpoint: pause only when a boolean condition is true.
Right-click a breakpoint → “More” or “Condition”, then enter an expression (e.g., numbers[i] == 40).
Log message / Print to console: configure a breakpoint to log text instead of pausing (helpful when you want tracing without stopping).
Method breakpoint: pause when a specific method is entered or exited (note: method breakpoints can be slower — use sparingly).
Exception breakpoint: pause when a particular exception is thrown (e.g., NullPointerException). Add via Run → View Breakpoints (or Ctrl+Shift+F8) → Java Exception Breakpoint.
Example (conditional):
for (int i = 0; i < numbers.length; i++) {
System.out.println("Processing number: " + numbers[i]); // set breakpoint here with condition numbers[i]==40
}
Expected behavior: the debugger pauses only when the evaluated condition is true.
2. Watchpoints (field watch)
A watchpoint suspends execution when a field is read or written. Use it to track when a shared/static/class-level field changes.
How to set:
Right-click a field declaration → “Toggle Watchpoint” (or add in the Debug tool window under Watches).
You can add conditions to watchpoints too (e.g., pause only when counter == 5).
Note: watchpoints work at the field level (class members). Local variables are visible in the Variables pane while stopped, but you can’t set a watchpoint on a local variable.
3. Exception breakpoints
If an exception is thrown anywhere, you may want the debugger to stop immediately where it originates.
How to set:
Run → View Breakpoints (or Ctrl+Shift+F8) → + → Java Exception Breakpoint → choose exception(s) and whether to suspend on “Thrown” and/or “Uncaught”.
This is invaluable to find the exact place an exception is raised (instead of chasing stack traces).
Here’s an expanded and more practical version of those sections. It keeps your tone consistent and adds real-world examples, common use cases, and small code snippets where helpful.
You can connect IntelliJ to port 5005 and debug as if the app were local.
Common use case: Your REST API behaves differently inside Docker. Attach debugger → Set breakpoints in your service → Reproduce the issue → Inspect environment-specific behavior.
9. Debugging unit tests (Practical usage)
Right-click a test and run in debug mode. Useful for:
Verifying mocks and stubbing
Tracking unexpected NPEs inside tests
Checking the correctness of assertions
Understanding why a particular test is flaky
Example: Your test fails:
assertEquals(100, service.calculateTotal(cart));
Set a breakpoint inside calculateTotal() and run the test in debug mode. You instantly see where values diverge.
10. Logs vs Breakpoints: when to use which (Practical usage)
Use both together depending on the situation.
Use logs when:
You need a history of events.
The issue happens only sometimes.
You want long-term telemetry.
It’s a production or staging environment.
Use breakpoints when:
You need to inspect exact values at runtime
You want to experiment with Evaluate Expression
You want to track control flow step-by-step
Log Message Breakpoints (super useful)
These let you print useful info without editing code.
Example: Instead of adding:
System.out.println("i = " + i);
You can configure a breakpoint to log:
"Loop index: " + i
and continue execution without stopping. This is ideal for debugging loops or repeated method calls without cluttering code.
Example walkthrough (putting the pieces together)
Open DebugExample.java in IntelliJ.
Toggle a breakpoint at System.out.println(“Processing number: ” + numbers[i]);.
Start debug (Shift+F9). Program runs and pauses when numbers[i] is 40.
Inspect variables in the Variables pane, add a watch for i and for numbers[i].
Use Evaluate Expression to compute numbers[i] * 2 or call helper methods.
If you change a method body and compile, accept HotSwap when IntelliJ prompts to reload classes
Common pitfalls & tips
Method/exception breakpoints can be slow if used everywhere — prefer line or conditional breakpoints for hotspots.
Conditional expressions should be cheap; expensive conditions slow down program execution during debugging.
Watchpoints are only for fields; for locals, use a breakpoint and the Variables pane.
HotSwap is limited — don’t rely on it for structural changes.
Remote debugging over public networks: Be careful exposing JDWP ports publicly — use SSH tunnels or secure networking.
Avoid changing production behavior (don’t connect a debugger to critical production systems without safeguards).
Handy keyboard shortcuts (Windows/Linux | macOS)
Toggle breakpoint: Ctrl+F8 | ⌘F8
Start debug: Shift+F9 | Shift+F9
Resume: F9 | F9
Step Over: F8 | F8
Step Into: F7 | F7
Smart Step Into: Shift+F7 | Shift+F7
Evaluate Expression: Alt+F8 | ⌥F8
View Breakpoints dialog: Ctrl+Shift+F8 | ⌘⇧F8
(Shortcuts can be mapped differently if you use an alternate Keymap.)
Key Takeaways
Debugging is essential because it helps you understand and fix unexpected behavior in your Java code beyond what logging or tests can reveal.
IntelliJ IDEA offers powerful debugging tools like breakpoints, conditional breakpoints, watchpoints, and exception breakpoints, which allow you to pause and inspect your code precisely.
Use features like Evaluate Expression and Watches to interactively test and verify your code’s logic while paused in the debugger.
Stepping through code (Step Over, Step Into, Step Out) helps uncover issues by following program flow in detail.
HotSwap allows quick code changes without restarting, therefore speeding up the debugging cycle.
Remote debugging lets you troubleshoot apps running in containers, servers, or other environments thereby, enabling seamless investigation.
Combine logs and breakpoints strategically depending on the situation, therefore, to maximize insight.
Familiarize yourself with keyboard shortcuts and IntelliJ’s debugging settings ultimately, for an efficient workflow.
Conclusion
In fact, IntelliJ’s debugger is powerful — from simple line breakpoints to remote attachment, watches, exception breakpoints, and HotSwap. As a result, practicing these workflows will make you faster at diagnosing issues and understanding complex code paths. Debugging Java code in IntelliJ. Start small: set a couple of targeted conditional breakpoints, step through the logic, use Evaluate Expression, and gradually add more advanced techniques like remote debugging or thread inspection.
An SDET with hands-on experience in the life science domain, including manual testing, functional testing, Jira, defect reporting, web application, and desktop application testing. I also have extensive experience in web and desktop automation using Selenium, WebDriver, WinAppDriver, Playwright, Cypress, Java, JavaScript, Cucumber, maven, POM, Xray, and building frameworks.
API Automation Testing Framework – In Today’s fast-paced digital ecosystem, almost every modern application relies on APIs (Application Programming Interfaces) to function seamlessly. Whether it’s a social media integration pulling live updates, a payment gateway processing transaction, or a data service exchanging real-time information, APIs act as the invisible backbone that connects various systems together.
Because APIs serve as the foundation of all interconnected software, ensuring that they are reliable, secure, and high performing is absolutely critical. Even a minor API failure can impact multiple dependent systems; consequently, it may cause application downtime, data mismatches, or even financial loss.
That’s where API automation testing framework comes in. Unlike traditional UI testing, API testing validates the core business logic directly at the backend layer, which makes it faster, more stable, and capable of detecting issues early in the development cycle — even before the frontend is ready.
In this blog, we’ll walk through the process of building a complete API Automation Testing Framework using a combination of:
Java – as the main programming language
Maven – for project and dependency management
Cucumber – to implement Behavior Driven Development (BDD)
RestAssured – for simplifying RESTful API automation
Playwright – to handle browser-based token generation
The framework you’ll learn to build will follow a BDD (Behavior-Driven Development) approach, enabling test scenarios to be written in simple, human-readable language. This not only improves collaboration between developers, testers, and business analysts but also makes test cases easier to understand, maintain, and extend.
Additionally, the API automation testing framework will be CI/CD-friendly, meaning it can be seamlessly integrated into automated build pipelines for continuous testing and faster feedback.
By the end of this guide, you’ll have a scalable, reusable, and maintainable API testing framework that brings together the best of automation, reporting, and real-time token management — a complete solution for modern QA teams.
What is API?
An API (Application Programming Interface) acts as a communication bridge between two software systems, allowing them to exchange information in a standardized way. In simpler terms, it defines how different software components should interact — through a set of rules, protocols, and endpoints.
Think of an API as a messenger that takes a request from one system, delivers it to another system, and then brings back the response. This interaction, therefore, allows applications to share data and functionality without exposing their internal logic or database structure.
Let’s take a simple example: When you open a weather application on your phone, it doesn’t store weather data itself. Instead, it sends a request to a weather server API, which processes the request and sends back a response — such as the current temperature, humidity, or forecast. This request-response cycle is what makes APIs so powerful and integral to almost every digital experience we use today.
Most modern APIs follow the REST (Representational State Transfer) architectural style. REST APIs use the HTTP protocol and are designed around a set of standardized operations, including:
HTTP Method
Description
Example Use
GET
Retrieve data from the server
Fetch a list of users
POST
Create new data on the server
Add a new product
PUT
Update existing data
edit user details
DELETE
Remove data
Delete a record
The responses returned by API’s are typically in JSON (JavaScript Object Notation) format – a lightweight, human-readable, and machine-friendly data format that’s easy to parse and validate.
In essence, API’s are the digital glue that holds modern applications together — enabling smooth communication, faster integrations, and a consistent flow of information across systems.
What is API Testing?
API Testing is the process of verifying that an API functions correctly and performs as expected — ensuring that all its endpoints, parameters, and data exchanges behave according to defined business rules.
In simple terms, it’s about checking whether the backend logic of an application works properly — without needing a graphical user interface (UI). Since APIs act as the communication layer between different software components, testing them helps ensure that the entire system remains reliable, secure, and efficient.
API testing typically focuses on four main aspects:
Functionality – Does the API perform the intended operation and return the correct response for valid requests?
Reliability – Does it deliver consistent results every time, even under different inputs and conditions?
Security – Is the API protected from unauthorized access, data leaks, or token misuse?
Performance – Does it respond quickly and remain stable under heavy load or high traffic?
Unlike traditional UI testing, which validates the visual and interactive parts of an application, API testing operates directly at the business logic layer. This makes it:
Faster – Since it bypasses the UI, execution times are much shorter.
More Stable – UI changes (like a button name or layout) don’t affect API tests.
Proactive – Tests can be created and run even before the front-end is developed.
In essence, API testing ensures the heart of your application is healthy. By validating responses, performance, and security at the API level, teams can detect defects early, reduce costs, and deliver more reliable software to users.
Why is API Testing Important?
API Testing plays a vital role in modern software development because APIs form the backbone of most applications. A failure in an API can affect multiple systems and impact overall functionality.
Here’s why API testing is important:
Ensures Functionality: Verifies that endpoints return correct responses and handle errors properly.
Enhances Security: Detects vulnerabilities like unauthorized access or token misuse.
Validates Data Integrity: Confirms that data remains consistent across APIs and databases.
Improves Performance: Checks response time, stability, and behavior under load.
Detects Defects Early: Allows early testing right after backend development, saving time and cost
Supports Continuous Integration: Easily integrates with CI/CD pipelines for automated validation.
In short, API testing ensures your system’s core logic is reliable, secure, and ready for real-world use.
Tools for Manual API Testing
Before jumping into automation, it’s essential to explore and understand APIs manually. Manual testing helps you validate endpoints, check responses, and get familiar with request structures.
Here are some popular tools used for manual API testing:
Postman: The most widely used tool for sending API requests, validating responses, and organizing test collections [refer link – https://www.postman.com/.
SoapUI: Best suited for testing both SOAP and REST APIs with advanced features like assertions and mock services.
Insomnia: A lightweight and user-friendly alternative to Postman, ideal for quick API exploration.
cURL: A command-line tool perfect for making fast API calls or testing from scripts.
Fiddler: Excellent for capturing and debugging HTTP/HTTPS traffic between client and server.
Using these tools helps testers understand API behavior, request/response formats, and possible edge cases — forming a strong foundation before moving to API automation.
Tools for API Automation Testing
After verifying APIs manually, the next step is to automate them using reliable tools and libraries. Automation helps improve test coverage, consistency, and execution speed.
Here are some popular tools used for API automation testing:
RestAssured: A powerful Java library designed specifically for testing and validating RESTful APIs.
Cucumber: Enables writing test cases in Gherkin syntax (plain English), making them easy to read and maintain.
Playwright: Automates browser interactions; in our framework, it will be used for token generation or authentication flows.
Postman + Newman: Allows you to run Postman collections directly from the command line — ideal for CI/CD integration.
JMeter: A robust tool for performance and load testing of APIs under different conditions.
In this blog, our focus will be on building a framework using RestAssured, Cucumber, and Playwright — combining functional, BDD, and authentication automation into one cohesive setup.
Framework Overview
We’ll build a Behavior-Driven API Automation Testing Framework that combines multiple tools for a complete testing solution. Here’s how each component fits in:
Cucumber – Manages the BDD layer, allowing test scenarios to be written in simple, readable feature files.
RestAssured – Handles HTTP requests and responses for validating RESTful APIs.
Playwright – Automates browser-based actions like token generation or authentication.
Maven – Manages project dependencies, builds, and plugins efficiently.
Cucumber HTML Reports – Automatically generates detailed execution reports after each run.
The framework follows a modular structure, with separate packages for step definitions, utilities, configurations, and feature files — ensuring clean organization, easy maintenance, and scalability.
In this, we will be creating a feature file for API Automation Testing Framework. A feature file consists of steps. These steps are mentioned in the gherkin language. The feature is easy to understand and can be written in the English language so that a non-technical person can understand the flow of the test scenario. In this framework we will be automating the four basic API request methods i.e. POST, PUT, GET and DELETE.
We can assign tags to our scenarios mentioned in the feature file to run particular test scenarios based on the requirement. The key point you must notice here is the feature file should end with .feature extension. We will be creating four different scenarios for the four different API methods.
Feature: All Notes API Validation
@api
Scenario Outline: Validate POST Create Notes API Response for "<scenarioName>" Scenario
When User sends "<method>" request to "<url>" with headers "<headers>" and query file "<queryFile>" and requestDataFile "<bodyFile>"
Then User verifies the response status code is <statusCode>
And User verifies the response body matches JSON schema "<schemaFile>"
Then User verifies fields in response: "<contentType>" with content type "<fields>"
Examples:
| scenarioName | method | url | headers | queryFile | bodyFile | statusCode | schemaFile | contentType | fields |
| Valid create Notes | POST | /api/v1/loan-syndications/{dealId}/investors/{investorId}/notes | NA | NA | Create_Notes_Request | 200 | NA | NA | NA |
Scenario Outline: Validate GET Notes API Response for "<scenarioName>" Scenario
When User sends "<method>" request to "<url>" with headers "<headers>" and query file "<queryFile>" and requestDataFile "<bodyFile>"
Then User verifies the response status code is <statusCode>
And User verifies the response body matches JSON schema "<schemaFile>"
Then User verifies fields in response: "<contentType>" with content type "<fields>"
Examples:
| scenarioName | method | url | headers | queryFile | bodyFile | statusCode | schemaFile | contentType | fields |
| Valid Get Notes | GET | /api/v1/loan-syndications/{dealId}/investors/{investorId}/notes | NA | NA | NA | 200 | Notes_Schema_200 | json | note=This is Note 1 |
Scenario Outline: Validate Update Notes API Response for "<scenarioName>" Scenario
When User sends "<method>" request to "<url>" with headers "<headers>" and query file "<queryFile>" and requestDataFile "<bodyFile>"
Then User verifies the response status code is <statusCode>
And User verifies the response body matches JSON schema "<schemaFile>"
Then User verifies fields in response: "<contentType>" with content type "<fields>"
Examples:
| scenarioName | method | url | headers | queryFile | bodyFile | statusCode | schemaFile | contentType | fields |
| Valid update Notes | PUT | /api/v1/loan-syndications/{dealId}/investors/{investorId}/notes/{noteId}/update-notes | NA | NA | Update_Notes_Request | 200 | NA | NA | NA |
Scenario Outline: Validate DELETE Create Notes API Response for "<scenarioName>" Scenario
When User sends "<method>" request to "<url>" with headers "<headers>" and query file "<queryFile>" and requestDataFile "<bodyFile>"
Then User verifies the response status code is <statusCode>
And User verifies the response body matches JSON schema "<schemaFile>"
Then User verifies fields in response: "<contentType>" with content type "<fields>"
Examples:
| scenarioName | method | url | headers | queryFile | bodyFile | statusCode | schemaFile | contentType | fields |
| Valid delete | DELETE | /api/v1/loan-syndications/{dealId}/investors/{investorId}/notes/{noteId} | NA | NA | NA | 200 | NA | NA | NA |
Step 4: Creating a Step Definition File
Unlike the automation framework which we have built in the previous blog, we will be creating a single-step file for all the feature files. In the BDD framework, the step files are used to map and implement the steps described in the feature file. Rest Assured library is very accurate to map the steps with the steps described in the feature file. We will be describing the same steps in the step file as they have described in the feature file so that behave will come to know the step implementation for the particular steps present in the feature file.
package org.Spurqlabs.Steps;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.response.Response;
import org.Spurqlabs.Core.TestContext;
import org.Spurqlabs.Utils.*;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import static org.Spurqlabs.Utils.DealDetailsManager.replacePlaceholders;
import static org.hamcrest.Matchers.equalTo;
public class CommonSteps extends TestContext {
private Response response;
@When("User sends {string} request to {string} with headers {string} and query file {string} and requestDataFile {string}")
public void user_sends_request_to_with_query_file_and_requestDataFile (String method, String url, String headers, String queryFile, String bodyFile) throws IOException {
String jsonString = Files.readString(Paths.get(FrameworkConfigReader.getFrameworkConfig("DealDetails")), StandardCharsets.UTF_8);
JSONObject storedValues = new JSONObject(jsonString);
String fullUrl = FrameworkConfigReader.getFrameworkConfig("BaseUrl") + replacePlaceholders(url);
Map<String, String> header = new HashMap<>();
if (!"NA".equalsIgnoreCase(headers)) {
header = JsonFileReader.getHeadersFromJson(FrameworkConfigReader.getFrameworkConfig("headers") + headers + ".json");
} else {
header.put("cookie", TokenManager.getToken());
}
Map<String, String> queryParams = new HashMap<>();
if (!"NA".equalsIgnoreCase(queryFile)) {
queryParams = JsonFileReader.getQueryParamsFromJson(FrameworkConfigReader.getFrameworkConfig("Query_Parameters") + queryFile + ".json");
for (String key : queryParams.keySet()) {
String value = queryParams.get(key);
for (String storedKey : storedValues.keySet()) {
value = value.replace("{" + storedKey + "}", storedValues.getString(storedKey));
}
queryParams.put(key, value);
}
}
Object requestBody = null;
if (!"NA".equalsIgnoreCase(bodyFile)) {
String bodyTemplate = JsonFileReader.getJsonAsString(
FrameworkConfigReader.getFrameworkConfig("Request_Bodies") + bodyFile + ".json");
for (String key : storedValues.keySet()) {
String placeholder = "{" + key + "}";
if (bodyTemplate.contains(placeholder)) {
bodyTemplate = bodyTemplate.replace(placeholder, storedValues.getString(key));
}
}
requestBody = bodyTemplate;
}
response = APIUtility.sendRequest(method, fullUrl, header, queryParams, requestBody);
response.prettyPrint();
TestContextLogger.scenarioLog("API", "Request sent: " + method + " " + fullUrl);
if (scenarioName.contains("GET Notes") && response.getStatusCode() == 200) {
DealDetailsManager.put("noteId", response.path("[0].id"));
}
}
@Then("User verifies the response status code is {int}")
public void userVerifiesTheResponseStatusCodeIsStatusCode(int statusCode) {
response.then().statusCode(statusCode);
TestContextLogger.scenarioLog("API", "Response status code: " + statusCode);
}
@Then("User verifies the response body matches JSON schema {string}")
public void userVerifiesTheResponseBodyMatchesJSONSchema(String schemaFile) {
if (!"NA".equalsIgnoreCase(schemaFile)) {
String schemaPath = "Schema/" + schemaFile + ".json";
response.then().assertThat().body(matchesJsonSchemaInClasspath(schemaPath));
TestContextLogger.scenarioLog("API", "Response body matches schema");
} else {
TestContextLogger.scenarioLog("API", "Response body does not have schema to validate");
}
}
@Then("User verifies field {string} has value {string}")
public void userVerifiesFieldHasValue(String jsonPath, String expectedValue) {
response.then().body(jsonPath, equalTo(expectedValue));
TestContextLogger.scenarioLog("API", "Field " + jsonPath + " has value: " + expectedValue);
}
@Then("User verifies fields in response: {string} with content type {string}")
public void userVerifiesFieldsInResponseWithContentType(String contentType, String fields) throws IOException {
// If NA, skip verification
if ("NA".equalsIgnoreCase(contentType) || "NA".equalsIgnoreCase(fields)) {
return;
}
String responseStr = response.getBody().asString().trim();
try {
if ("text".equalsIgnoreCase(contentType)) {
// For text, verify each expected value is present in response
for (String expected : fields.split(";")) {
expected = replacePlaceholders(expected.trim());
if (!responseStr.contains(expected)) {
throw new AssertionError("Expected text not found: " + expected);
}
TestContextLogger.scenarioLog("API", "Text found: " + expected);
}
} else if ("json".equalsIgnoreCase(contentType)) {
// For json, verify key=value pairs
JSONObject jsonResponse;
if (responseStr.startsWith("[")) {
JSONArray arr = new JSONArray(responseStr);
jsonResponse = !arr.isEmpty() ? arr.getJSONObject(0) : new JSONObject();
} else {
jsonResponse = new JSONObject(responseStr);
}
for (String pair : fields.split(";")) {
if (pair.trim().isEmpty()) continue;
String[] kv = pair.split("=", 2);
if (kv.length < 2) continue;
String keyPath = kv[0].trim();
String expected = replacePlaceholders(kv[1].trim());
Object actual = JsonFileReader.getJsonValueByPath(jsonResponse, keyPath);
if (actual == null) {
throw new AssertionError("Key not found in JSON: " + keyPath);
}
if (!String.valueOf(actual).equals(String.valueOf(expected))) {
throw new AssertionError("Mismatch for " + keyPath + ": expected '" + expected + "', got '" + actual + "'");
}
TestContextLogger.scenarioLog("API", "Validated: " + keyPath + " = " + expected);
}
} else {
throw new AssertionError("Unsupported content type: " + contentType);
}
} catch (AssertionError | Exception e) {
TestContextLogger.scenarioLog("API", "Validation failed: " + e.getMessage());
throw e;
}
}
Step 5: Creating API
Till now we have successfully created a feature file and a step file now in this step we will be creating a utility file. Generally, in Web automation, we have page files that contain the locators and the actions to perform on the web elements but in this framework, we will be creating a single utility file just like the step file. The utility file contains the API methods and the endpoints to perform the specific action like, POST, PUT, GET, or DELETE. The request body i.e. payload and the response body will be captured using the methods present in the utility file. So the reason these methods are created in the utility file is that we can use them multiple times and don’t have to create the same method over and over again.
package org.Spurqlabs.Utils;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import java.io.File;
import java.util.Map;
public class APIUtility {
public static Response sendRequest(String method, String url, Map<String, String> headers, Map<String, String> queryParams, Object body) {
RequestSpecification request = RestAssured.given();
if (headers != null && !headers.isEmpty()) {
request.headers(headers);
}
if (queryParams != null && !queryParams.isEmpty()) {
request.queryParams(queryParams);
}
if (body != null && !method.equalsIgnoreCase("GET")) {
if (headers == null || !headers.containsKey("Content-Type")) {
request.header("Content-Type", "application/json");
}
request.body(body);
}
switch (method.trim().toUpperCase()) {
case "GET":
return request.get(url);
case "POST":
return request.post(url);
case "PUT":
return request.put(url);
case "PATCH":
return request.patch(url);
case "DELETE":
return request.delete(url);
default:
throw new IllegalArgumentException("Unsupported HTTP method: " + method);
}
}
Step 6: Create a Token Generation using Playwright
In this step, we automate the process of generating authentication tokens using Playwright. Many APIs require login-based tokens (like cookies or bearer tokens), and managing them manually can be difficult — especially when they expire frequently.
The TokenManager class handles this by:
Logging into the application automatically using Playwright.
Extracting authentication cookies (OauthHMAC, OauthExpires, BearerToken).
Storing the token in a local JSON file for reuse.
Refreshing the token automatically when it expires.
This ensures that your API tests always use a valid token without manual updates, making the framework fully automated and CI/CD ready.
package org.Spurqlabs.Utils;
import java.io.*;
import java.nio.file.*;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Cookie;
public class TokenManager {
private static final ThreadLocal<String> tokenThreadLocal = new ThreadLocal<>();
private static final ThreadLocal<Long> expiryThreadLocal = new ThreadLocal<>();
private static final String TOKEN_FILE = "token.json";
private static final long TOKEN_VALIDITY_SECONDS = 30 * 60; // 30 minutes
public static String getToken() {
String token = tokenThreadLocal.get();
Long expiry = expiryThreadLocal.get();
if (token == null || expiry == null || Instant.now().getEpochSecond() >= expiry) {
// Try to read from a file (for multi-JVM/CI)
Map<String, Object> fileToken = readTokenFromFile();
if (fileToken != null) {
token = (String) fileToken.get("token");
expiry = ((Number) fileToken.get("expiry")).longValue();
}
// If still null or expired, fetch new
if (token == null || expiry == null || Instant.now().getEpochSecond() >= expiry) {
Map<String, Object> newToken = generateAuthTokenViaBrowser();
token = (String) newToken.get("token");
expiry = (Long) newToken.get("expiry");
writeTokenToFile(token, expiry);
}
tokenThreadLocal.set(token);
expiryThreadLocal.set(expiry);
}
return token;
}
private static Map<String, Object> generateAuthTokenViaBrowser() {
String bearerToken;
long expiry = Instant.now().getEpochSecond() + TOKEN_VALIDITY_SECONDS;
int maxRetries = 2;
int attempt = 0;
Exception lastException = null;
while (attempt < maxRetries) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(true));
BrowserContext context = browser.newContext();
Page page = context.newPage();
// Robust wait for login page to load
page.navigate(FrameworkConfigReader.getFrameworkConfig("BaseUrl"), new Page.NavigateOptions().setTimeout(60000));
page.waitForSelector("#email", new Page.WaitForSelectorOptions().setTimeout(20000));
page.waitForSelector("#password", new Page.WaitForSelectorOptions().setTimeout(20000));
page.waitForSelector("button[type='submit']", new Page.WaitForSelectorOptions().setTimeout(20000));
// Fill a login form
page.fill("#email", FrameworkConfigReader.getFrameworkConfig("UserEmail"));
page.fill("#password", FrameworkConfigReader.getFrameworkConfig("UserPassword"));
page.waitForSelector("button[type='submit']:not([disabled])", new Page.WaitForSelectorOptions().setTimeout(10000));
page.click("button[type='submit']");
// Wait for either dashboard element or flexible URL match
boolean loggedIn;
try {
page.waitForSelector(".dashboard, .main-content, .navbar, .sidebar", new Page.WaitForSelectorOptions().setTimeout(20000));
loggedIn = true;
} catch (Exception e) {
// fallback to URL check
try {
page.waitForURL(url -> url.startsWith(FrameworkConfigReader.getFrameworkConfig("BaseUrl")), new Page.WaitForURLOptions().setTimeout(30000));
loggedIn = true;
} catch (Exception ex) {
// Both checks failed
loggedIn = false;
}
}
if (!loggedIn) {
throw new RuntimeException("Login did not complete successfully: dashboard element or expected URL not found");
}
// Extract cookies
String oauthHMAC = null;
String oauthExpires = null;
String token = null;
for (Cookie cookie : context.cookies()) {
switch (cookie.name) {
case "OauthHMAC":
oauthHMAC = cookie.name + "=" + cookie.value;
break;
case "OauthExpires":
oauthExpires = cookie.name + "=" + cookie.value;
if (cookie.expires != null && cookie.expires > 0) {
expiry = cookie.expires.longValue();
}
break;
case "BearerToken":
token = cookie.name + "=" + cookie.value;
break;
}
}
if (oauthHMAC != null && oauthExpires != null && token != null) {
bearerToken = oauthHMAC + ";" + oauthExpires + ";" + token + ";";
} else {
throw new RuntimeException("❗ One or more cookies are missing: OauthHMAC, OauthExpires, BearerToken");
}
browser.close();
Map<String, Object> map = new HashMap<>();
map.put("token", bearerToken);
map.put("expiry", expiry);
return map;
} catch (Exception e) {
lastException = e;
System.err.println("[TokenManager] Login attempt " + (attempt + 1) + " failed: " + e.getMessage());
attempt++;
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
}
}
throw new RuntimeException("Failed to generate auth token after " + maxRetries + " attempts", lastException);
}
private static void writeTokenToFile(String token, long expiry) {
try {
Map<String, Object> map = new HashMap<>();
map.put("token", token);
map.put("expiry", expiry);
String json = new Gson().toJson(map);
Files.write(Paths.get(TOKEN_FILE), json.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
private static Map<String, Object> readTokenFromFile() {
try {
Path path = Paths.get(TOKEN_FILE);
if (!Files.exists(path)) return null;
String json = new String(Files.readAllBytes(path));
return new Gson().fromJson(json, new TypeToken<Map<String, Object>>() {}.getType());
} catch (IOException e) {
return null;
}
}
}
Step 7: Create Framework Config File
A good tester is one who knows the use and importance of config files. In this framework, we are also going to use the config file. Here, we are just going to put the base URL in this config file and will be using the same in the utility file over and over again. The config file contains more data than just of base URL when you start exploring the framework and start automating the new endpoints then at some point, you will realize that some data can be added to the config file.
Additionally, the purpose of the config files is to make tests more maintainable and reusable. Another benefit of a config file is that it makes the code more modular and easier to understand as all the configuration settings are stored in a separate file and it makes it easier to update the configuration settings for all the tests at once.
At this stage, we create the TestRunner class, which serves as the entry point to execute all Cucumber feature files. It uses TestNG as the test executor and integrates Cucumber for running BDD-style test scenarios.
The @CucumberOptions annotation defines:
features → Location of all .feature files.
glue → Packages containing step definitions and hooks.
plugin → Reporting options like JSON and HTML reports.
After execution, Cucumber automatically generates:
Cucumber.json → For CI/CD and detailed reporting.
Cucumber.html → A user-friendly HTML report showing test results.
This setup makes it easy to run all API tests and view clean, structured reports for quick analysis.
Once the framework is set up, you can execute your API automation suite directly from the command line using Maven. Maven handles compiling, running tests, and generating reports automatically.
Run All Tests –
To run all Cucumber feature files:
mvn clean test
clean → Deletes old compiled files and previous reports for a fresh run.
test → Executes all test scenarios defined in your project.
After running this command, Maven will trigger the Cucumber TestRunner, execute all scenarios, and generate reports in the test-output folder.
Run Tests by Tag –
Tags allow you to selectively run specific test scenarios or features. You can add tags like @api1, @smoke, or @regression in your .feature files to categorize tests.
Example:
@api1
Scenario: Verify POST API creates a record successfully
Given User sends "POST" request to "/api/v1/create" ...
Then User verifies the response status code is 201
To execute only scenarios with a specific tag, use:
mvn clean test -Dcucumber.filter.tags="@api1"
The framework will run only those tests that have the tag @api1.
You can combine tags for more flexibility:
@api1 or @api2 → Runs tests with either tag.
@smoke and not @wip → Runs smoke tests excluding work-in-progress scenarios.
This is especially useful when running specific test groups in CI/CD pipelines.
View Test Reports
API Automation Testing Framerwork Report – After the execution, Cucumber generates detailed reports automatically in the test-output directory:
Cucumber.html → User-friendly HTML report showing scenario results and logs.
Cucumber.json → JSON format report for CI/CD integrations or analytics tools.
You can open the report in your browser:
project-root/test-output/Cucumber.html
This section gives testers a clear understanding of how to:
API automation testing framework ensures that backend services are functioning properly before the application reaches the end user. Therefore, by integrating Cucumber, RestAssured, and Playwright, we have built a flexible and maintainable test framework that:
Supports BDD style scenarios.
Handles token-based authentication automatically.
Provides reusable utilities for API calls.
Generates rich HTML reports for easy analysis.
This hybrid setup helps QA engineers achieve faster feedback, maintain cleaner code, and enhance the overall quality of the software.
I am an Jr. SDET Engineer skilled in Manual and Automation Testing (UI & API). Proficient in Selenium, Cucumber, TestNG, Postman, RestAssured, Maven, SQL, GitHub, Jenkins, Java, JavaScript, HTML, and CSS. Experienced in CI/CD integration, framework design, and ensuring high-quality software delivery.
Behavior Driven Development (BDD) is a process that promotes collaboration between developers, testers, and stakeholders by writing test cases in simple, plain language. BDD Automation Frameworks like Cucumber use Gherkin to make test scenarios easily understandable and link them to automated tests.
In this guide, we’ll show you how to create a BDD Automation Framework using Java and Playwright. Playwright is a powerful browser automation tool, and when combined with Java and Cucumber, it creates a solid BDD testing framework.
Introduction to BDD Automation Framework:
Automation testing is testing software with the latest tools and technologies with developed scripts in less time. In Automation testing it involves test case execution, data validation, and result reporting.
Why Playwright over Selenium?
Playwright is an open-source Node.js library that further enables efficient end-to-end (E2E) testing of web applications. As Playwright offers better performance speed than Selenium. Also, Playwright offers various features like Cross-Brower support, Multi-platform, Headless and Headful Mode, Async/Await API, Integration with Testing Frameworks.
What is BDD Automation Framework?
BDD framework is an agile approach to test software where testers write test cases in simple language so that non-tech person can also understand the flow. Moreover, it enhances collaboration between the technical team and the business team. We use Gherkin language to write feature files, making them easily readable by everyone.
Prerequisites for BDD Automation Framework:
1. Install JDK
Install the Java environment as per the system compatible.
First, choose the appropriate JDK version, and then click on the download link for the Windows version.
Run the Installer:
Once the download is complete, run the installer.
To begin, follow the installation instructions, then accept the license agreement, and finally choose the installation directory.
Set Environment Variables:
Open the Control Panel and go to System and Security > System > Advanced system settings.
Click on “Environment Variables”.
Under “System Variables,” click on “New” and add a variable named JAVA_HOME with the path to the JDK installation directory (e.g., C:\Program Files\Java\jdk-15).
Find the “Path” variable in the “System Variables” section, click on “Edit,” and add a new entry with the path to the bin directory inside the JDK installation directory (e.g., C:\Program Files\Java\jdk-15\bin).
Verify Installation:
Open a Command Prompt and check if Java is installed correctly by typing `java -version` and `javac -version`.
Click on the link to download the binary zip archive (e.g., apache-maven-3.x.y-bin.zip).
Extract the Archive:
Extract the downloaded zip file to a suitable directory (e.g., C:\Program Files\Apache\maven).
Set Environment Variables:
Open the Control Panel and go to System and Security > System > Advanced system settings.
Click on “Environment Variables”.
Under “System Variables”, click on “New” and add a variable named MAVEN_HOME with the path to the Maven installation directory (e.g., C:\Program Files\Apache\maven\apache-maven-3.x.y).
Find the “Path” variable in the “System Variables” section, click on “Edit”, and add a new entry with the path to the bin directory inside the Maven installation directory (e.g., C:\Program Files\Apache\maven\apache-maven-3.x.y\bin).
Verify Installation:
To check if Maven is installed correctly, open a Command Prompt and type `mvn -version`.
Java Development Kit (JDK): Ensure you have JDK installed and properly configured.
Maven or Gradle: Depending on your preference, however, you’ll need Maven or Gradle to manage your project dependencies.
Steps to Install Cucumber with Maven
Create a Maven Project:
Update pom.xml File:
Open the pom.xml file in your project.
This Maven POM file (pom.xml) defines project metadata, dependencies on external libraries (Cucumber, Selenium, Playwright), and Maven build properties. It provides the necessary configuration for managing dependencies, compiling Java source code, and integrating with Cucumber, TestNG, Selenium, and Playwright frameworks to support automated testing and development of the CalculatorBDD project.
Before starting the project on the BDD Automation Framework:
Create a new Maven project in your IDE.
Add the dependencies in Pom.xml file .
Create folder structure following steps given below:
When we created the new project for the executable jar file, we could see the simple folder structure provided by Maven.
SRC Folder: The SRC folder is the parent folder of a project, and it will also include the main and test foldersIn the QA environment, we generally use the test folder, while we reserve the main folder for the development environment. The development team uses the main folder, so the created JAR contains all the files inside the src folder.
Test Folder: Inside the test folder; additionally, Java and resources folders are available.
Java Folder: This folder primarily contains the Java classes where the actual code is present.
Resources Folder: The Resources folder contains the resources file, test data file, and document files.
Pom.xml: In this file, we are managing the dependencies and plugins that are required for automation.
As our project structure is ready so we can start with the BDD framework:
1. Feature file:
Here we have described the scenario in “Gherkin” language which is designed to be easily understandable by non-technical stakeholders as well as executable by automation tools like Cucumber. Each scenario is written in structured manner using keywords “Given”, “When” and “Then”. Calculator.feature in this we have specifically written our functional testing steps.
@Basic
Feature: Verify Calculator Operations
Scenario Outline: Verify addition of two numbers
#Given line states that the User is on the Calculator home page and Calculator page is displayed.
Given I am on Calculator page
#When step describes an action that User enters/clicks on a number
When I enter number <number>
#And step indicates clicking on a specific operator (like addition, subtraction, etc.) on the calculator
And I click on operator '<operator>'
#And Step follows the operator click by entering another number into the calculator.
And I enter number <number1>
#Then is the verification step where the test checks if the result displayed
Then I verify the result as <expectedResult>
Examples:
| number | operator | number1 | expectedResult |
| 5 | + | 2 | 7 |
| 9 | - | 3 | 6 |
| 6 | * | 4 | 24 |
| 2 | / | 2 | 1 |
2. Step Def File:
The step definition file serves as the bridge between actual feature file with the actual method implementation in the page file. The Calculator steps are a step definition file that maps the feature file to the page file and functional implementation.
package steps;
import core.TestContext;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.testng.Assert;
import pages.CalculatorPage;
import java.io.IOException;
public class CalculatorSteps extends TestContext {
public CalculatorSteps() {
//Here the constructor initializes a new instance of CalculatorPage,
// so the CalculatorSteps class can interact with the calculator web page through methods in the CalculatorPage class
calculatorPage = new CalculatorPage();
}
@Given("I am on Calculator page")
//Here we call the function from Calculator Page which is Sync with Feature file Given definition
public void iAmOnCalculatorPage() throws IOException {
calculatorPage.iAmOnCalculatorPage();
}
@When("I enter number {int}")
//Here also the function is called from page file and Sync with feature file When step
public void iEnterNumber(int number) {
calculatorPage.iEnterNumber(number);
}
@And("I click on operator {string}")
//Here also the function is called from page file and sync with feature file And step
public void iClickOnOperator(String operator) {
calculatorPage.iClickOnOperator(operator);
}
@Then("I verify the result as {int}")
//Here also the function is called from page file and synched with feature file Then step
public void iVerifyTheResultAs(int expectedResult) {
String actualResult = calculatorPage.iVerifyTheResultAs();
Assert.assertEquals(actualResult, String.valueOf(expectedResult));
}
}
3. Page File:
Page file, in addition, is actual code implementation from the step definition file.Here, we have saved all the actual methods and web page elements, thereby ensuring easy access and organization. It is basically POM structure. So here we are performing addition operation in Calculator we application so created a method to click on a number and another method for clicking on the operator. Here we can minimize the code by reusing the code as much as possible.
package pages;
import core.TestContext;
import utilities.ConfigUtil;
import java.io.IOException;
public class CalculatorPage extends TestContext {
public void iAmOnCalculatorPage() throws IOException {
page.navigate(ConfigUtil.getPropertyValue("base_url"));
}
public void iEnterNumber(int number) {
page.locator("//span[@onclick='r(" + number + ")']").click();
}
public void iClickOnOperator(String operator) {
page.locator("//span[@onclick=\"r('" + operator + "')\"]").click();
}
public String iVerifyTheResultAs() {
page.locator("//span[@onclick=\"r('=')\"]").click();
return page.locator("//div[@id='sciOutPut']").innerText().trim();
}
public void tearDown() {
page.close();
}
}
4. Hooks:
Hooks are setup and teardown methods that, therefore, are written separately in the configuration class. Here we have annotation declare in the hooks file @before and @After. Hooks are steps to be performed a before and after function of the feature file. In this we have open the Web browser in Before and After Tag. These are special functions which allows the testers to execute specific points during execution.
package core;
import core.TestContext;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import utilities.WebUtil;
public class Hooks extends TestContext {//Hooks class inherits the property of TestContext class
@Before //@Before Tag denotes that it should be executed before scenario
public void beforeScenario(Scenario scenario) {
page = WebUtil.initBrowser(); //this method initializes the browser session
}
@After //@After Tag denotes that it should be executed after scenario
public void afterScenario() {
WebUtil.tearDownPW();//this method is for tasks such as closing browser sessions
}
}
5. TestContext:
The TestContext class, moreover, holds various instances and variables required for test execution. In this context, we have successfully created a web driver instance, a page file instance, and a browser context. As a result, the code reusability, organization, and maintainability are improved here.
package core;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.Page;
import pages.CalculatorPage;
public class TestContext { //TestContext class, which acts as a container to store all instances for test framework
public static Page page;
//Refers to Playwright’s Page object. This controls a specific browser tab or page in a Playwright-based test
public static CalculatorPage calculatorPage;
//This stores an instance of the CalculatorPage object, representing the page object model (POM)
public static Browser browser;
//refers to Playwright's Browser instance, which represents the entire browser
}
6. TestRunner:
The Test Runner is responsible for discovering test cases, executing them, and reporting the results back; additionally, it provides the necessary infrastructure to execute the tests and manage the testing workflow. It also syncs the feature file with step file.
package core;
import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
import org.testng.annotations.DataProvider;
@CucumberOptions(features = "src/test/java/features", //the path where Cucumber feature files are located.
glue = {"steps", "core"}) //Cucumber where to find the step definitions (in the steps and core packages)
public class TestRunner extends AbstractTestNGCucumberTests {
//Above Etends which is a base class provided by Cucumber to run the tests with TestNG
@DataProvider //allows running multiple Cucumber scenarios as separate tests in TestNG
@Override
public Object[][] scenarios() {
return super.scenarios();
}//Calls the parent class method to return all the Cucumber scenarios in an array format for TestNG to run
}
7. WebUtils:
Web Utils is a file in which browser instance is created and playwright is initialised here. The code for web browser page launching is written here and for closing the browser instance. The page is extended by TestContext where all the properties of TestContext are given to WebUtils page.
package utilities;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import core.TestContext;
public class WebUtil extends TestContext {
public static Page initBrowser(){
//Initializes a browser session using Playwright's Chromium browser
Playwright playwright = Playwright.create(); //Creates an instance of Playwright
browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false));
page = browser.newPage(); //Creates a new page/tab within the launched browser
return page;
}
public static void tearDownPW() {
page.close();
} // It is called to close the current page/tag
}
This is the important file where we download all the dependencies required for the test execution. Also, it contains information of project and configuration information for the maven to build the project such as dependencies, build directory, source directory, test source directory, plugin, goals etc.
In this blog, we’ve discussed using the Java Playwright framework with Cucumber for BDD. Playwright offers fast, cross-browser testing and easy parallel execution, making it a great alternative to Selenium. Paired with Cucumber, it helps teams write clear, automated tests. Playwright’s debugging tools and test isolation also reduce test issues and maintenance, making it ideal for building reliable test suites for faster, higher-quality software delivery.
Web tables, also known as HTML tables, are a widely used format for displaying data on web pages. They allow for a structured representation of information in rows and columns, making it easy to read and manipulate data. Selenium WebDriver, a powerful tool for web browser automation, provides the functionality to interact with these tables programmatically. This capability is beneficial for tasks like web scraping, automated testing, and data validation. In this blog, we will see how to extract data from Web tables in Java-Selenium.
Identify web table from your webpage:
To effectively identify and interact with web tables using Selenium, it’s crucial to understand the HTML structure of tables and the specific tags used. Here’s an overview of the key table-related HTML tags
A typical HTML table consists of several tags that define its structure:
<table>: The main container for the table.
<thead>: Defines the table header, which contains header rows (<tr>).
<tbody>: Contains the table body, which includes the data rows.
<tr>:Defines a table row.
<th>: Defines a header cell in a table row.
<td>: Defines a standard data cell in a table row.
As a demo website, here you will get a sample WebTable with fields like first name, last name, email, etc. Here we have applied a filter for email to minimize the size of the table.
We will be starting by launching the browser and navigating to the webpage. We have applied a filter for the email “PolGermain@whatever.com”, you can change it as per your requirement.
Once we get the filtered data from the table, now we need to locate the table and get the number of rows. The table will have multiple rows so, we need to use a list to store all the rows.
As we have stored all the rows in the list, now we need to iterate through each rows to fetch the columns and store the column data in another list.
Example :
Abc
1
Xyz
2
table has 2 rows and 2 columns
When we are iterating through the 1st row we will get data as Abc and 1 and store it in the list ’as rowdata[Abc, 1] similarly data from the 2nd row will be stored as rowdata[Xyz, 2].When we are iterating through the 2nd row the data from the 1st row will be overwritten. That’s why we will need one more list ‘webRows ’ to store all the rows. In the below code snippet, here we are iterating through all the columns from each row one by one and finally storing all the rows in the list WebRows.
We have successfully extracted the table data now you can use this data as per your requirement
To do this we need to iterate through the list ‘webRows’ where we have our table data stored. We will be accessing all the columns by their index. In this case, you should know the column index you want to access. The column index always starts from 0.
for (int s = 0; s < webRows.size(); s++) {
List<String> row = webRows.get(s);
System.out.println(row.get(1));
System.out.println(row);
}
Below is the complete code snippet for the above-mentioned steps. You need to update related Xpaths in case you are not able to access the rows and columns with the given Xpaths.
Instead of accessing data by the index, you can access it using the column index also, and to do that you need to use the HashMaps instead of lists. HashMap will help to store column headers as keys and column data as values
Example:
Name
Id
Abc
1
Xyz
2
Table has 3 rows and 2 columns
Here Name and ID will be your keys and Abc, 1 and Xyz, 2 will be the values.
How to store and access table data using HashMap?
The code snippet below shows how to use HashMap to store data in key-value format.
package Selenium;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.ArrayList;
import java.util.List;
public class Webtable_Blog {
public static void main(String[] args) throws InterruptedException {
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
driver.get("https://www.globalsqa.com/angularJs-protractor/WebTable/");
driver.manage().window().maximize();
WebElement global_search = driver.findElement(By.xpath("//input[@type='search' and @placeholder='global search']"));
global_search.sendKeys("PolGermain@whatever.com");
// global_search.sendKeys("Pol");
global_search.sendKeys(Keys.ENTER);
Thread.sleep(5000);
List<WebElement> rows = driver.findElements(By.xpath("//table[@class='table table-striped']/tbody/tr"));
System.out.println("size-"+rows.size());
List<Map<String, String>> webRows = new ArrayList<>();
for (int i = 0; i < rows.size(); i++) {
List<WebElement> keys = driver.findElements(By.xpath("//table[@class='table table-striped']/thead/tr[1]/th"));
List<WebElement> values = driver.findElements(By.xpath("//table[@class='table table-striped']/tbody/tr["+(i+1)+"]/td"));
Map<String, String> webColumn = new HashMap<>();
try {
for (int j = 0; i < keys.size(); j++) {
webColumn.put(keys.get(j).getText(), values.get(j).getText());
}
} catch (Exception e) {
}
webRows.add(webColumn);
}
for (int s = 0; s < webRows.size(); s++) {
System.out.println(webRows.get(s).get("lastName"));
System.out.println(webRows.get(s));
}
}
}
In this blog, we’ve delved into the powerful capabilities of Selenium WebDriver for handling web tables in Java. WebTables are a crucial part of web applications, often used to display large amounts of data in an organized manner. In Java Selenium, handling these WebTables efficiently is a key skill for any test automation engineer. Throughout this blog, we’ve explored various techniques to interact with WebTables, including locating tables, accessing rows and cells, iterating through table data, and performing actions like sorting and filtering.
Click here for more blogs on software testing and test automation.
Priyanka is an experienced SDET with 4+ years in functional, regression, and mobile testing across IoT, Life Sciences, and HCM domains. She excels in building automation frameworks using Selenium, Playwright, Appium, and Cucumber, with strong skills in API testing (Postman, Rest Assured) and database validation (MySQL, PostgreSQL). ISTQB certified and proficient in agile environments, she ensures high-quality delivery through automation, cross-browser testing, and CI/CD integration.
Working with PDF documents programmatically can be a challenging task, especially when you need to extract and manipulate text content. However, with the right tools and libraries, you can efficiently convert PDF text to a structured JSON format.
Converting PDF to JSON programmatically offers flexibility and customization, especially in dynamic runtime environments where reliance on external tools may not be feasible. While free tools exist, they may not always cater to specific runtime requirements or integrate seamlessly into existing systems.
Consider scenarios like real-time data extraction from PDF reports generated by various sources. During runtime, integrating with a specific tool might not be viable due to constraints such as security policies, network connectivity, or the need for real-time processing. In such cases, a custom-coded solution allows for on-the-fly conversion tailored to the application’s needs.
For Example:
E-commerce Invoice Processing: Extracting invoice details and converting them to JSON for real-time database updates.
Healthcare Records Management: Converting patient records to JSON for integration with EHR systems, ensuring HIPAA compliance.
Legal Document Analysis: Extracting specific clauses and dates from legal documents for analysis.
Free tools are inadequate for real-time, automated, and secure PDF to JSON conversion. Coding your own solution ensures efficient, scalable, and compliant data handling.
In this blog, we’ll walk through a Java program that accomplishes using the powerful iTextPDF and Jackson libraries. Screenshots will be included to illustrate the process in Testing.
Introduction for Converting PDF to JSON in Java
PDF documents are ubiquitous in the modern world, used for everything from reports and ebooks to invoices and forms. They provide a versatile way to share formatted text, images, and even interactive content. Despite their convenience, PDFs can be difficult to work with programmatically, especially when you need to extract specific information from them.
Often, there arises a need to extract text content from PDFs for various purposes such as:
Data Analysis: Extracting textual data for analysis, reporting, or further processing.
Indexing: Creating searchable indexes for large collections of PDF documents.
Transformation: Converting PDF content into different formats like JSON, XML, or CSV for interoperability with other systems.
JSON (JavaScript Object Notation) is a lightweight data interchange format that’s easy for humans to read and write, and easy for machines to parse and generate. It is widely used in web applications, APIs, and configuration files due to its simplicity and versatility.
In this guide, we will explore how to convert the text content of a PDF file into a JSON format using Java. We’ll leverage the iTextPDF library for PDF text extraction and the Jackson library for JSON processing. This approach will allow us to take advantage of the structured nature of JSON to organize the extracted text in a meaningful way.
Prerequisites for Converting PDF to JSON in Java
Before we dive into the code, ensure you have the following prerequisites installed and configured:
Java Development Kit (JDK)
Maven for managing dependencies
iTextPDF library for handling PDF documents
Jackson library for JSON processing
Step-by-Step Installation and Setup for Converting PDF to JSON in Java
Install Java Development Kit (JDK)
The JDK is a software development environment used for developing Java applications. To install the JDK:
Start IntelliJ IDEA: Open from the start menu (Windows).
Complete Initial Setup: Import settings or start fresh.
Start a New Project: Begin a new project or open an existing one.
Open IntelliJ IDEA:
Launch IntelliJ IDEA on your computer
Create or Open a Project
If you already have a project, open it. Otherwise, create a new project by selecting File > New > Project….
Name your project and select the project location
Choose Java from Language.
Choose Maven from the Build systems.
Select the project SDK (JDK) and click Next.
Choose the project template (if any) and click Next.
Then click Create.
Create a New Java Class
In the Project tool window (usually on the left side), right-click on the (src → test → java) directory or any of its subdirectories where you want to create the new class.
Select New > Java Class from the context menu.
Name Your Class
In the dialog that appears, enter the name of your new class. For example, you can name it PdfToJsonConversion.
Click OK/Enter.
Add the following dependencies to your pom.xml file for Converting PDF to JSON in Java:
<dependencies>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-core</artifactId>
<version>8.0.3</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.0</version> <!-- Use the same version for consistency -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4.1</version> <!-- Use the latest version available -->
</dependency>
<!-- Jackson Annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version> <!-- Use the latest version available -->
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.8.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
Write Your Code to Convert PDF to JSON in Java
IntelliJ IDEA will create a new .java file with the name you provided.
You can start writing your Java code inside this file.
The Java Program to Covert PFT to JSON
Here is the complete Java program that converts a PDF file to JSON:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PdfToJsonConversion {
@Test
public static void convertPdfFileToJson() {
String inputPdfPath = "C:\\Users\\Mangesh\\Downloads\\What is Software Testing.pdf";
String outputJsonPath = "src/test/java/What is Software Testing.json";
List<String> contentList = new ArrayList<>();
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputPdfPath))) {
int numPages = pdfDoc.getNumberOfPages();
for (int i = 1; i <= numPages; i++) {
PdfPage page = pdfDoc.getPage(i);
String pageContent = PdfTextExtractor.getTextFromPage(page);
contentList.add(pageContent);
}
} catch (IOException e) {
e.printStackTrace();
}
// Create JSON object
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
ArrayNode pagesArray = mapper.createArrayNode();
// Add page contents to JSON array
for (int i = 0; i < contentList.size(); i++) {
ObjectNode pageNode = mapper.createObjectNode();
pageNode.put("Page", i + 1);
// Split content by lines and add to JSON object with line number as key
String[] lines = contentList.get(i).split("\\r?\\n");
ObjectNode linesObject = mapper.createObjectNode();
for (int j = 0; j < lines.length; j++) {
linesObject.put(Integer.toString(j + 1), lines[j]);
}
pageNode.set("Content", linesObject);
pagesArray.add(pageNode);
}
File outputJsonFile = new File(outputJsonPath);
// Write JSON to file
try {
mapper.writeValue(outputJsonFile, pagesArray);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Content stored in " + outputJsonFile.getName());
}
}
Explanation
Let’s break down the code step by step:
1. Dependencies
Jackson Library:
ObjectMapper, SerializationFeature, ArrayNode, ObjectNode: These are from the Jackson library, used for creating and manipulating JSON objects.
iText Library:
PdfDocument, PdfPage, PdfReader, PdfTextExtractor: These classes are from the iText library, used for reading and extracting text from PDF documents.
TestNG Library:
@Test: An annotation from the TestNG library, used for marking the convertPdfFileToJson method as a test method.
Java Standard Library:
File, IOException, ArrayList, List: Standard Java classes for file operations, handling exceptions, and working with lists.
2. Test Annotation
The class PdfToJsonConversion contains a static method convertPdfFileToJson which is annotated with @Test, making it a test method in a TestNG test class.
3. Method convertPdfFileToJson:
This method handles the core functionality of reading a PDF and converting its content to JSON.
4. Input and Output Paths:
inputPdfPath specifies the PDF file location, and outputJsonPath defines where the resulting JSON file will be saved.
5. PDF to Text Conversion:
Create a PdfDocument object using a PdfReader for the input PDF file.
Get the number of pages in the PDF.
Loop through each page, extract text using PdfTextExtractor, and add the text to contentList.
Handle any IOException that may occur.
6. Creating JSON Objects:
Create an ObjectMapper for JSON manipulation.
Enable pretty printing with SerializationFeature.INDENT_OUTPUT.
Create an ArrayNode to hold the content of each page.
7. Adding Page Content to JSON:
Iterate over contentList to process each page’s content.
For each page, create an ObjectNode and set the page number.
Split the page content into lines, then create another ObjectNode to hold each line with its number as the key.
Add the linesObject to the pageNode and then add the pageNode to pagesArray.
8. Writing JSON to File
Create a File object for the output JSON file.
Use the ObjectMapper to write pagesArray to the JSON file, handling any IOException.
Print a confirmation message indicating the completion of the process.
9. Output
The program outputs the name of the JSON file once the conversion is complete.
Running the Program
To run this program, ensure you have the required libraries in your project’s classpath. You can run it through your IDE or using a build tool like Maven.
Open your IDE and load the project.
Ensure dependencies are correctly set in your pom.xml.
Run the test method convertPdfFileToJson.
You should see output similar to this in your console: Content stored in What is Software Testing.json. The JSON file will be created in the specified output path.
JSON Output Example
Here’s a snippet of what the JSON output might look like.
[ {
"Page" : 1,
"Content" : {
"1" : "What is Software Testing? ",
"2" : "Last Updated : 24 May, 2024 ",
"3" : " ",
"4" : " ",
"5" : "",
"6" : "Software testing can be stated as the process of verifying and validating whether a ",
"7" : "software or application is bug-free, meets the technical requirements as guided by "
}
}, {
"Page" : 2,
"Content" : {
"1" : " Increased customer satisfaction: Software testing ensures reliability, security, ",
"2" : "and high performance which results in saving time, costs, and customer "
}
} ]
Conclusion
Converting PDF text content to JSON can greatly simplify data processing and integration tasks. With Java, the iTextPDF, and Jackson libraries, this task becomes straightforward and efficient. This guide provides a comprehensive example to help you get started with your own PDF to JSON conversion projects. https://github.com/mangesh-31/PdfToJsonConversion
Hello! I’m Mangesh, a Software Tester SDET. In my professional life, I focus on ensuring the quality of software products through thorough testing and analysis. I have been learning and working with Selenium, Java, and Playwright to develop automated testing solutions. Currently, I am working Jr. SDET at SpurQLabs Technologies Pvt. Ltd.