Schema Validation in KarateBDD using a Reusable Java Method

Himanshu PandeyHimanshu Pandey
6 min read

❓ Why Validating Schema for API Tests is Important?

Schema validation is more than a "nice-to-have" — it’s a critical guardrail for modern API testing. Here's why we need it :

🔥 Top 5 Reasons to Validate JSON Schema in API Testing

  • Catch Contract Breaks Early
    Changes in field names, data types, or required fields often break consumers downstream. Schema validation acts as a first line of defense.

  • 🕊 Ensure Backward Compatibility
    In microservices and public APIs, maintaining consistent structure across versions is crucial. Schema validation ensures your responses remain predictable.

  • 🚀 Boost Confidence in CI/CD Deployments
    Automated schema checks in your pipeline prevent broken APIs from being promoted to higher environments or production.

  • 🧠 Validate Optional and Nested Structures
    Complex APIs often include optional fields, polymorphic types, or deeply nested structures. JSON schema handles these scenarios with precision.

  • 🧯 Reduce Regression Bugs
    Developers may unknowingly alter response structures. Schema tests catch such regressions even when functional outputs still "look correct."


💬 Real-World Scenarios

🛳 For a Global Shipping Platform

“Schema validation saved us from shipping an API that silently dropped a required field after a refactor. Functional tests passed, but schema validation caught it — and saved our clients from breaking.”


🏥 For a Healthcare SaaS Provider

“Our APIs deal with EHRs and HL7/FHIR data. We thought integration tests were enough, but schema validation caught malformed fields that would have caused downstream rejections by insurance providers.”


🛒 For an E-Commerce Checkout Service

“We introduced a ‘discountCode’ field, but a typo in the payload caused it to go missing for 10% of orders. Schema tests added in staging picked it up, and we avoided hundreds of support calls post-deploy.”


📚 Table of Contents

  1. 🔧 Prerequisites

  2. 📦 Step 1: Add Maven Dependencies

  3. 🧠 Step 2: Create Reusable Java Method

  4. 🧾 Step 3: Create JSON Schema File

  5. � Step 4: Call Validator in Karate Feature File

  6. ⚙️ Step 5: Make It Framework-Agnostic

  7. 💡 Bonus Tips

  8. 📌 Summary

1. 🔧 Prerequisites

Before we begin, make sure you have the following setup on your machine:

RequirementDetails
☑️ JavaJDK 11 or later installed and added to your PATH
☑️ MavenInstalled and working (mvn -v should succeed)
☑️ KarateBDDA Karate project scaffolded and ready to run tests
☑️ IDE or EditorIntelliJ IDEA, VSCode, or any preferred IDE
☑️ Internet AccessRequired to download Maven dependencies
☑️ Resource FolderCreate a folder like src/test/resources/schema/ for schema files

Note: KarateBDD Maven Quickstart setup:

Set up a Karate Maven project using the official quickstart guide. It helps scaffold your project correctly with all necessary dependencies and structure.


📦 Step 1: Add Maven Dependencies

Add these dependencies to your pom.xml:

<dependency>
    <groupId>com.github.erosb</groupId>
    <artifactId>everit-json-schema</artifactId>
    <version>1.14.1</version>
</dependency>

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20211205</version>
</dependency>

🧠 Step 2: Create Reusable Java Method

Create JsonSchemaValidator.java in your utils/ directory:

package utils;

import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONTokener;

import java.io.InputStream;

public class JsonSchemaValidator {

    public static boolean validate(String schemaPath, String jsonData) {
        try (InputStream schemaInputStream = JsonSchemaValidator.class.getResourceAsStream(schemaPath)) {
            if (schemaInputStream == null) {
                throw new RuntimeException("Schema file not found: " + schemaPath);
            }

            JSONObject rawSchema = new JSONObject(new JSONTokener(schemaInputStream));
            Schema schema = SchemaLoader.load(rawSchema);

            String trimmedJsonData = jsonData.trim();
            if (trimmedJsonData.startsWith("[")) {
                schema.validate(new JSONArray(trimmedJsonData));
            } else if (trimmedJsonData.startsWith("{")) {
                schema.validate(new JSONObject(trimmedJsonData));
            } else {
                throw new RuntimeException("Invalid JSON data format.");
            }

            return true;
        } catch (Exception e) {
            throw new RuntimeException("Schema validation failed for data: '" +
                jsonData.substring(0, Math.min(jsonData.length(), 100)) + "...'. Error: " + e.getMessage(), e);
        }
    }
}

🧾 Step 3: Create JSON Schema File

Save this as products-schema.json under src/test/resources/schema/:

{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "type": "array",
    "items": {
        "$ref": "#/definitions/Welcome10Element"
    },
    "definitions": {
        "Welcome10Element": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "id": {
                    "type": "string",
                    "format": "integer"
                },
                "name": {
                    "type": "string"
                },
                "data": {
                    "anyOf": [
                        {
                            "$ref": "#/definitions/Data"
                        },
                        {
                            "type": "null"
                        }
                    ]
                }
            },
            "required": [
                "data",
                "id",
                "name"
            ],
            "title": "Welcome10Element"
        },
        "Data": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "color": {
                    "type": "string"
                },
                "capacity": {
                    "type": "string"
                },
                "capacity GB": {
                    "type": "integer"
                },
                "price": {
                    "type": "number"
                },
                "generation": {
                    "type": "string"
                },
                "year": {
                    "type": "integer"
                },
                "CPU model": {
                    "type": "string"
                },
                "Hard disk size": {
                    "type": "string"
                },
                "Strap Colour": {
                    "type": "string"
                },
                "Case Size": {
                    "type": "string"
                },
                "Color": {
                    "type": "string"
                },
                "Description": {
                    "type": "string"
                },
                "Capacity": {
                    "type": "string"
                },
                "Screen size": {
                    "type": "number"
                },
                "Generation": {
                    "type": "string"
                },
                "Price": {
                    "type": "string"
                }
            },
            "required": [],
            "title": "Data"
        }
    }
}

Note: If you have not being officially given any schema as part of the requirements, you can generate the schema using online tools like json-formatter

🧪 Step 4: Call Validator in Karate Feature File

Feature: Validate API response schema

Background: 
  Given url 'https://api.restful-api.dev'
  * def validator = Java.type('utils.JsonSchemaValidator')

Scenario: Validate schema
  Given path '/objects'
  When method GET
  Then status 200

  * string responseString = karate.toJson(response)
  * def matchingSchema = validator.validate('/resources/schema/products-schema.json',responseString)
  And match matchingSchema == true

Note: When comparing json , if you get following error, keep in the mind the following when passing response object to call the “validate” method for comparison

>>>> 01: validator.validate('/resources/schema/products-schema.json',responseString) <<<< org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (validate) on utils.JsonSchemaValidator failed due to: Cannot convert '[{"id":"1","name":"Google Pixel 6 Pro","data":{"color":"Cloudy White","capacity":"128 GB"}},{"id":"2","name":"Apple iPhone 12 Mini, 256GB...'(language: Java, type: com.intuit.karate.graal.JsList) to Java type 'java.lang.String': Invalid or lossy primitive coercion. - <js>.:program(Unnamed:1)

  •   * def responseString = karate.toJson(response) 
                              Vs 
      * string responseString = karate.toJson(response)
    
  • In Karate, def is used for JavaScript variable assignments.

  • When response is a JSON array (as suggested by the JsList type and the example data [{...}, {...}]), karate.toJson(response) might convert the Java/Karate representation of the response into a JavaScript array/list object within the GraalVM JavaScript engine that Karate uses.

  • This JavaScript array is then seen by the Java side (your JsonSchemaValidator) as a com.intuit.karate.graal.JsList, not as a plain java.lang.String that contains the JSON text.

  • Therefore, when validator.validate (which is a Java method) tries to use this JsList as a String, it fails with the "Cannot convert" error because there's no direct, implicit conversion from a JsList to a String in a way that would produce the JSON string representation.

>>>>
01: validator.validate('/resources/schema/products-schema.json',responseString)
<<<<
org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (validate) on utils.JsonSchemaValidator failed due to: Cannot convert '[{"id":"1","name":"Google Pixel 6 Pro","data":{"color":"Cloudy White","capacity":"128 GB"}},{"id":"2","name":"Apple iPhone 12 Mini, 256GB...'(language: Java, type: com.intuit.karate.graal.JsList) to Java type 'java.lang.String': Invalid or lossy primitive coercion.
- <js>.:program(Unnamed:1)
0
Subscribe to my newsletter

Read articles from Himanshu Pandey directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Himanshu Pandey
Himanshu Pandey