Skip to main content
Back to Blog
BDD
2026-05-07

Karate BDD API Testing: Complete Guide 2026

Complete guide to API testing with Karate DSL. Installation, request/response patterns, JSON path assertions, GraphQL, mocking, performance testing, parallel execution, and CI integration for 2026.

Karate BDD API Testing: Complete Guide 2026

Karate is the BDD framework you choose when most of your tests are API-driven. While Cucumber, SpecFlow, and Behave focus on UI scenarios with step definitions in Java/C#/Python, Karate takes a radically different approach: it builds a domain-specific HTTP testing language directly on top of Gherkin syntax. The result is API tests that read like English specifications but execute as if they were hand-written HTTP clients -- with built-in JSON/XML matchers, schema validation, GraphQL support, performance testing, and even API mocking baked in.

In 2026, Karate is one of the fastest-growing BDD frameworks, particularly in microservices-heavy organizations where API contracts dominate the test surface. This guide walks through everything you need to adopt Karate at scale: installation, project structure, feature file syntax, JSON path assertions, dynamic test data, mocking, performance testing with Gatling, parallel execution, and CI integration. Every example is production-tested against Karate 1.5.x running on JVM 21.

By the end you will have a complete picture of Karate's capabilities, when it wins over Cucumber + RestAssured, and how to combine it with Playwright for hybrid UI + API suites.

Key Takeaways

  • Karate eliminates step definitions -- HTTP verbs and assertions are built into the framework.
  • JSON path matching with fuzzy operators (#string, #uuid, #regex) makes assertions concise.
  • Schema validation via response matching catches breaking changes.
  • Karate Mock Server provides production-grade API mocking from the same Gherkin syntax.
  • Karate Gatling integration lets you reuse functional tests as load tests.

1. Project Setup

Add Karate as a Maven dependency:

<dependency>
  <groupId>io.karatelabs</groupId>
  <artifactId>karate-junit5</artifactId>
  <version>1.5.0</version>
  <scope>test</scope>
</dependency>

Or Gradle:

testImplementation 'io.karatelabs:karate-junit5:1.5.0'

Standard directory structure:

src/test/java/
  com/example/api/
    users/
      users.feature
      UsersTest.java
    orders/
      orders.feature
      OrdersTest.java
  karate-config.js

The karate-config.js is the global setup file:

function fn() {
  var env = karate.env || 'dev';
  var config = {
    env: env,
    baseUrl: 'http://localhost:8080',
    apiKey: 'test-key-123'
  };
  if (env === 'staging') {
    config.baseUrl = 'https://staging-api.example.com';
    config.apiKey = karate.properties['staging.apiKey'];
  }
  if (env === 'prod') {
    config.baseUrl = 'https://api.example.com';
    config.apiKey = karate.properties['prod.apiKey'];
  }
  return config;
}

2. Your First Feature File

Feature: User CRUD operations

  Background:
    * url baseUrl
    * header Content-Type = 'application/json'
    * header Authorization = 'Bearer ' + apiKey

  @smoke
  Scenario: Create a new user
    Given path '/users'
    And request { name: 'Alice', email: 'alice@example.com' }
    When method post
    Then status 201
    And match response == { id: '#uuid', name: 'Alice', email: 'alice@example.com', createdAt: '#string' }
    * def userId = response.id

  Scenario: Read the created user
    Given path '/users', userId
    When method get
    Then status 200
    And match response.name == 'Alice'

  Scenario: Update the user
    Given path '/users', userId
    And request { name: 'Alice Smith' }
    When method patch
    Then status 200
    And match response.name == 'Alice Smith'

  Scenario: Delete the user
    Given path '/users', userId
    When method delete
    Then status 204

Notice there are no step definitions. Karate's runtime knows what Given url, When method post, and Then status 201 mean.

3. JSON Matchers

Karate's matchers are the framework's killer feature:

Scenario: Fuzzy matching examples
  Given path '/users'
  When method get
  Then status 200
  And match response ==
    """
    {
      total: '#number',
      items: '#[2..10]',
      items[0]: { id: '#uuid', name: '#string', email: '#regex .+@.+\\..+' }
    }
    """
MatcherMeaning
#stringAny string
#numberAny number
#booleanAny boolean
#uuidUUID format
#regex patternMatches a regex
#notnullAnything non-null
#ignoreIgnored (don't check)
#[N..M]Array of length between N and M
#presentKey must exist

4. Reusable Helpers and Functions

Karate features can call other features for shared setup. Create a helpers folder:

# src/test/java/com/example/helpers/create-user.feature
Feature: Create a user (helper)

  Scenario:
    Given url baseUrl
    And path '/users'
    And request { name: '#(name)', email: '#(email)' }
    When method post
    Then status 201
    * def created = response

Call from another feature:

Feature: Order operations

  Background:
    * url baseUrl
    * def userResult = call read('classpath:com/example/helpers/create-user.feature') { name: 'Buyer', email: 'buyer@example.com' }
    * def buyer = userResult.created

  Scenario: Place an order for the user
    Given path '/orders'
    And request { userId: '#(buyer.id)', total: 99.99 }
    When method post
    Then status 201

5. Data-Driven Tests

Karate supports CSV, JSON, and inline tables:

Scenario Outline: Validate user creation rules
  Given path '/users'
  And request { name: '<name>', email: '<email>' }
  When method post
  Then status <status>

  Examples:
    | name      | email             | status |
    | Alice     | alice@example.com | 201    |
    |           | bob@example.com   | 400    |
    | Charlie   | not-an-email      | 400    |

CSV-driven scenarios:

Scenario Outline:
  Given path '/users'
  And request { name: '<name>', email: '<email>' }
  When method post
  Then status <status>

  Examples:
    | read('users.csv') |

6. GraphQL Support

Karate handles GraphQL elegantly:

Scenario: GraphQL query for user details
  Given url baseUrl
  And path '/graphql'
  And request
    """
    {
      query: "query GetUser($id: ID!) { user(id: $id) { id name email orders { id total } } }",
      variables: { id: "#(userId)" }
    }
    """
  When method post
  Then status 200
  And match response.data.user == { id: '#(userId)', name: '#string', email: '#string', orders: '#[]' }

7. Mock Server

Karate's mock server uses the same Gherkin syntax to define responses:

# src/test/java/mocks/payments-mock.feature
Feature: Payments service mock

  Background:
    * configure cors = true

  Scenario: pathMatches('/charge') && methodIs('post')
    * def card = request.cardNumber
    * if (card == '4000-0000-0000-0002') karate.abort()
    * def response = { id: 'ch_' + new Date().getTime(), status: 'succeeded', amount: request.amount }
    * def responseStatus = 201

  Scenario: pathMatches('/charge') && methodIs('post')
    * def response = { error: 'card_declined' }
    * def responseStatus = 402

Start the mock in tests:

import com.intuit.karate.core.MockServer;

class PaymentsMockTest {
  @Test
  void startMock() {
    MockServer server = MockServer.feature("classpath:mocks/payments-mock.feature").http(8090).build();
    // use baseUrl http://localhost:8090
    server.stop();
  }
}

8. Performance Testing with Karate Gatling

Reuse the same .feature files as load tests:

class UsersLoadSimulation extends Simulation {
  val createUsers = scenario("create users").exec(karateFeature("classpath:com/example/api/users/users.feature"))
  setUp(createUsers.inject(rampUsers(100) during (30.seconds))).protocols(karateProtocol())
}

Run with Gatling Maven plugin. The same functional tests now produce load reports without any duplication.

9. Parallel Execution

Use the Karate Runner with parallel:

@Karate.Test
Karate runAll() {
  return Karate.run().relativeTo(getClass()).parallel(5);
}

5 parallel threads typically handle 200-500 scenarios per minute on a modern CI runner.

10. CI Integration

GitHub Actions example:

name: API Tests
on: [push]
jobs:
  karate:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 21 }
      - run: mvn -B test -Dkarate.env=staging
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: karate-report, path: target/karate-reports/ }

11. When to Use Karate vs Cucumber + RestAssured

AspectKarateCucumber + RestAssured
Step definitionsNone neededRequired
Learning curveLowerHigher
Polyglot teamsKarate-onlyReuses Java skills
GraphQLNativeRequires libraries
Mock serverBuilt-inSeparate tool
Performance testingKarate GatlingSeparate tool
Custom Java logicPossible but awkwardNative
Community sizeGrowingMassive

Choose Karate when API testing dominates and you want minimal step-definition maintenance. Choose Cucumber + RestAssured when you need extensive Java integration or already have a Cucumber UI suite.

12. AI-Assisted Karate Authoring

The karate SKILL.md pack on QASkills teaches Claude or Cursor to generate Karate features from OpenAPI specs. Combined with cursor-skills-md-best-practices, this lets you scaffold API test suites in minutes.

13. Advanced Karate Features

Custom Matchers

Beyond the built-in #string and #uuid, you can write custom matchers:

* def isFutureDate = function(d){ return new Date(d).getTime() > Date.now() }
Then match response.expiry == '#? isFutureDate(_)'

Chaining HTTP Calls

Scenario: Create order and pay
  Given path '/orders'
  And request { items: [...] }
  When method post
  Then status 201
  * def orderId = response.id

  Given path '/orders', orderId, 'pay'
  And request { amount: 99.99 }
  When method post
  Then status 200

Polling with Retry

* configure retry = { count: 5, interval: 2000 }
Given path '/jobs', jobId
And retry until response.status == 'COMPLETED'
When method get
Then status 200

Multipart Uploads

Given path '/upload'
And multipart file file = { read: 'test.pdf', filename: 'test.pdf', contentType: 'application/pdf' }
And multipart field title = 'Test Document'
When method post
Then status 201

WebSocket Testing

Karate supports WebSocket testing via the websocket keyword. Useful for chat APIs and real-time pubsub systems.

14. Karate's JavaScript Layer

Karate uses GraalJS to embed JavaScript in feature files. This lets you write helper functions inline:

* def computeHmac = function(payload, secret) { return Java.type('com.example.HmacUtil').compute(payload, secret) }
* def signature = computeHmac(request, apiSecret)
Given header X-Signature = signature

The JavaScript layer is one of Karate's most powerful features but also its most controversial. It lets you do almost anything but can make features hard to read.

15. Karate Mock Server Deep Dive

The Karate mock server is genuinely production-grade:

  • Supports HTTPS via configured certificates.
  • Records and replays real HTTP traffic.
  • Conditional responses based on request headers, paths, methods.
  • Stateful mocks (e.g., counter that increments per request).
  • Can be embedded in JUnit tests or run standalone.

Example stateful mock:

Feature: Idempotency mock

Background:
  * def attempts = {}

Scenario: pathMatches('/charge') && methodIs('post')
  * def key = requestHeaders['Idempotency-Key'][0]
  * if (attempts[key]) responseStatus = 200
  * if (attempts[key]) response = attempts[key]
  * def newResp = { id: 'ch_' + new Date().getTime(), status: 'succeeded' }
  * attempts[key] = newResp
  * def response = newResp
  * def responseStatus = 201

16. Karate vs Postman / Newman

AspectKaratePostman + Newman
Test as codeYes (Gherkin)JS code in collections
Version controlNativeJSON collections
Schema validationNative fuzzy matchersJSON schema plugin
Mock serverBuilt-inMockoon (separate)
Performance testingKarate GatlingLimited (Postman Cloud)
CI integrationMaven/Gradle nativeNewman CLI

For teams that prefer code over GUI, Karate wins. For teams that want manual exploratory + automated workflows, Postman often wins.

17. Karate vs RestAssured

AspectKarateRestAssured
LanguageGherkin DSLJava fluent API
Step definitionsNoneNone
Learning curveLowerHigher
Java integrationPossibleNative
ReportingKarate HTMLTestNG/JUnit

RestAssured is more integrated with Java workflows; Karate is more declarative.

18. CI/CD Integration

GitHub Actions:

jobs:
  karate:
    runs-on: ubuntu-22.04
    strategy:
      matrix: { shard: [1, 2, 3, 4] }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 21 }
      - run: mvn -B test -Dkarate.env=staging -Dkarate.options="--tags @smoke"
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: karate-report-${{ matrix.shard }}, path: target/karate-reports/ }

19. Frequently Asked Questions

Q: Can Karate test UI? A: Karate has a UI driver but it's less mature than Playwright or Selenium. For UI use those; for API use Karate.

Q: Does Karate work with GraphQL subscriptions? A: Yes via WebSocket primitives, but the syntax is more verbose than REST.

Q: Can I integrate Karate with Cucumber-JVM? A: They share Gherkin parsing but run independently. Most teams keep them separate.

Q: Karate's mock server in production-like scenarios? A: It handles tens of thousands of requests per second; suitable for performance testing or stand-in services in CI.

Q: AI agents for Karate? A: Yes -- the karate SKILL.md pack on QASkills teaches Claude or Cursor to generate Karate features from OpenAPI specs.

Conclusion

Karate in 2026 is the most ergonomic BDD framework for API-heavy testing. The combination of zero step definitions, fuzzy JSON matchers, GraphQL support, and built-in mocking is hard to beat. Pair it with Playwright for UI scenarios and you have a complete BDD stack. See comparing-popular-bdd-frameworks-2026-complete-guide for context on where Karate fits in the broader BDD landscape.

Karate BDD API Testing: Complete Guide 2026 | QASkills.sh