Schema Validation with Karate's match Syntax

9 min read

The API Testing Masterclass lesson on JSON Schema validation showed you how to define an expected response shape in a .json file and validate the response against it. In Rest Assured that means importing io.rest-assured:json-schema-validator, writing a separate schema file, and calling .body(matchesJsonSchemaInClasspath("user-schema.json")). In Karate, schema validation is inline — you write the expected shape directly in the scenario using fuzzy markers, and the match keyword enforces it. No extra library, no separate file (unless you want one).

Inline schema validation

The simplest form: a match block that describes the expected shape of the entire response:

Scenario: Validate user response schema
  Given path 'users', 1
  When method get
  Then status 200
  And match response ==
  """
  {
    id: '#number',
    name: '#string',
    email: '#regex .+@.+\\..+',
    role: '#? _ == "admin" || _ == "tester" || _ == "viewer"',
    active: '#boolean',
    createdAt: '#string',
    address: {
      street: '#string',
      city: '#string',
      postcode: '#string'
    },
    tags: '#array',
    phone: '##string'
  }
  """

The triple-quoted """ block is Karate's multiline string syntax — it makes complex match expressions readable. Each field uses a fuzzy marker: #number, #string, #boolean, #array, #regex, #?, or ##string for optional fields.

This single match block does what a JSON Schema file does, in-place, with no import.

#? — custom validation expressions

#? lets you write any boolean JavaScript expression as a validation rule. The _ placeholder is the current field value:

# Role must be one of three allowed values
role: '#? _ == "admin" || _ == "tester" || _ == "viewer"'
 
# ID must be a positive integer
id: '#? _ > 0'
 
# Name must be non-empty
name: '#? _.length > 0'
 
# Price must be within range
price: '#? _ >= 0 && _ <= 10000'

#? is Karate's equivalent of a JSON Schema enum or minimum/maximum constraint, expressed as readable JavaScript.

Schema validation for arrays — match each

When the response is a list, match each applies the schema to every element:

Scenario: Validate all users match the schema
  Given path 'users'
  When method get
  Then status 200
  And match each response ==
  """
  {
    id: '#number',
    name: '#string',
    email: '#string',
    username: '#string'
  }
  """

One assertion. Every object in the list is validated against the schema. If any user is missing a field or has the wrong type, the assertion fails and the failure message identifies which element and which field broke.

Reusable schema — def

When the same schema appears in multiple scenarios, extract it into a def:

Background:
  * url baseUrl
  * def userSchema =
  """
  {
    id: '#number',
    name: '#string',
    email: '#string',
    role: '#string'
  }
  """
 
Scenario: Single user matches schema
  Given path 'users', 1
  When method get
  Then status 200
  And match response == userSchema
 
Scenario: All users match schema
  Given path 'users'
  When method get
  Then status 200
  And match each response == userSchema

The schema defined in Background is available to all scenarios in the file. Both the single-object and array assertions reuse the same definition — change the schema once and both tests update.

Schema from a file

For large or complex schemas shared across multiple feature files, store the schema in a JSON file and load it with read():

* def userSchema = read('classpath:schemas/user-schema.json')
Then match response == userSchema

user-schema.json uses the same fuzzy marker syntax as an inline match:

{
  "id": "#number",
  "name": "#string",
  "email": "#regex .+@.+\\..+",
  "role": "#string",
  "active": "#boolean"
}

This is the closest Karate equivalent to the JSON Schema approach. The format is simpler than JSON Schema's draft specification, and you can use it with match == or match each identically to an inline assertion.

Karate inline schema vs JSON Schema file

Schema validation: JSON Schema file vs Karate inline match

JSON Schema (Rest Assured)

  • Separate .json file per resource

    Stored in src/test/resources/schemas/

  • Verbose draft-07 syntax

    type, properties, required arrays, $ref for reuse

  • Imported via json-schema-validator

    Extra Maven dependency required

  • Industry standard — works with any language

    Supported by Postman, Pact, OpenAPI

Karate match markers

  • Inline in the feature file

    No separate file needed for simple schemas

  • Compact marker syntax

    #string, #number, #regex, #?, ## for optional

  • Zero extra dependencies

    match is a built-in keyword

  • File-based schemas also supported

    read('classpath:schemas/user.json') for sharing

⚠️ Common mistakes

  • Using match == when you only care about a subset of fields. If the response has 15 fields and your schema specifies 5, match == fails because of the 10 unexpected fields. Use match contains for partial schema validation and match == only when you want to assert the exact, complete shape.
  • Forgetting to escape regex backslashes. In a Karate string, \\. is a literal \. in the regex (matching any character followed by a dot). A common mistake: writing #regex .+@.+\.+ (one backslash) instead of #regex .+@.+\\..+ (double backslash, matching a literal dot followed by more characters). Test your regex patterns against a real response before committing.
  • Defining the schema inside a scenario instead of Background when it's shared. A schema defined with def inside a scenario is only available in that scenario. If three scenarios all validate against the same schema, define it once in Background or in a shared JSON file. Duplicated inline schemas drift apart over time.

🎯 Practice task

Write schema validation at three levels of reuse. 35–45 minutes.

  1. GET /users/1 from JSONPlaceholder. Write an inline match response == with the triple-quoted block. Use #number for id, #string for name and email, and ##string for phone and website (optional). Run green.
  2. Add a #? assertion: id: '#? _ > 0' and name: '#? _.length > 0'. Verify both markers pass. Force id: '#? _ > 100' — confirm the failure message shows the actual id value.
  3. GET /users and write match each response == using a schema that covers id, name, email, and username. Confirm it runs across all 10 users in the list.
  4. Extract the schema into Background as a def. Rewrite both the single-user and multi-user scenarios to reference userSchema. Confirm both still pass.
  5. Create a file src/test/java/schemas/user-schema.json with the fuzzy-marker schema. Load it with * def userSchema = read('classpath:schemas/user-schema.json') in Background. Delete the inline def and confirm the tests still pass — the external file is now the single source of truth.
  6. Negative schema test: write a scenario that POSTs to /users with a body missing the email field. If the API returns a 400 or 422, validate the error response schema: match response contains { error: '#string' } or similar (JSONPlaceholder won't return this, but write it as if the API does — practice the pattern).
  7. Stretch: look at the API Testing Masterclass lesson on JSON Schema validation and compare the JSON Schema draft-07 syntax for the same user schema you wrote in step 1. Note which fields map to type: string, which map to pattern, and which would need oneOf for enum validation. This comparison is the clearest way to see what Karate's markers are replacing.

Next chapter: variables, data, and reuse — reading external files, def in depth, Scenario Outline for data-driven testing, and calling feature files from other feature files.

// tip to track lessons you complete and pick up where you left off across devices.