Karate's step keyword set is fixed and there is no way to register a new one. In a Karate feature file, how do you run logic Karate has no keyword for, and what does `Java.type('com.example.util.Signer')` give you?
answer
- You cannot add a keyword
- Two routes only: JavaScript or Java
- Java.type loads a class into a variable
- Bare java.util.UUID reference works too
- JSON crosses as plain Map and List
basics
~20 sWrite it as JavaScript in the feature file, or reach a Java class with Java.type('com.example.util.Signer'), which loads that class and returns it so its static methods and its constructor are callable from a step. JSON arrives there as plain Map and List.
solid answer
~40 sThere is no third option — you cannot add a keyword. Karate's vocabulary comes from Karate's own source: no service loader for step keywords, no annotation you can put on a class of your own, no directory the runner scans. So logic goes one of two ways. **JavaScript in the feature file**, either inline (`* def uuid = function(){ return java.util.UUID.randomUUID() + '' }`) or in a `.js` file the feature reads. Or **Java interop**: `* def Signer = Java.type('com.example.util.Signer')` loads that class and binds it to a variable, after which `Signer.sign(body)` calls a static method and `new Signer()` constructs one; a bare fully-qualified reference such as `java.util.UUID.randomUUID()` works too. A JSON payload crosses that seam as a plain `Map<String, Object>` or `List<Map<String, Object>>` — there is no object-mapping layer to configure.
code
gherkin · 13 linesFeature: sign an outgoing payload
Background:
* url baseUrl
* def Signer = Java.type('com.example.util.Signer')
Scenario: post a signed body
* def body = { amount: 100, currency: 'EUR' }
Given path 'payments'
And header X-Signature = Signer.sign(body)
And request body
When method post
Then status 201go deeper
Remember there is no way to add a keyword. Logic goes into a JavaScript function in the feature file, or into a Java class you reach with Java.type.
Explain the interop seam concretely: Java.type binds a class, static calls and constructors work from a step, and JSON crosses as a plain Map or List.
Own the boundary. Algorithms belong in compiled, unit-tested Java behind Java.type; the interaction stays in the feature file where a reviewer can see it.
Make it a standard, because the failure is cultural: once whole scenarios hide behind one Java call, the team has a Java suite wearing a feature file.
## The vocabulary is Karate's, not yours This is the first thing to be precise about, because it is the fact the whole leaf hangs on. Karate's step keywords — `def`, `set`, `json`, `xml`, `csv`, `copy`, `match`, `assert`, `print`, `url`, `path`, `param`, `header`, `request`, `method`, `status`, `soap action`, `retry until`, `multipart file`, `call`, `callonce`, `eval`, `configure` and the rest — are a fixed table built out of Karate's own code. Nothing in either line of the project offers a way to add to it: - there is no service loader for step keywords; - there is no annotation you can put on a class of your own to contribute one; - there is no directory of yours the runner scans for step code; - a word that is not in the table is simply not a keyword, and the step fails. That is *why* there is no step-definition layer, and it is also why the question "how do I add a step?" has no answer. The right question is "where does my logic go?", and there are exactly two places. ## Route one: JavaScript in the feature file Karate expressions are largely JavaScript, so a function is a value like any other: - inline, as a `def` — `* def uuid = function(){ return java.util.UUID.randomUUID() + '' }`; - in a `.js` file the feature reads, so several features share it; - as a called feature with `call` or `callonce`, when the reusable unit is itself a set of steps. This keeps the logic visible next to the scenario, which is the whole appeal of the model. It also means the logic is unchecked text, so it is the wrong home for anything you would want a compiler and unit tests to guard. ## Route two: Java interop `Java.type('com.example.util.Signer')` loads that class and hands the feature file a handle to it. From there the class behaves the way a JavaScript binding to a Java type behaves: | What you write | What it does | |---|---| | `* def Signer = Java.type('com.example.util.Signer')` | binds the class to a variable | | `Signer.sign(body)` | calls a static method | | `Signer.VERSION` | reads a static field | | `new Signer()` | constructs an instance | | `java.util.UUID.randomUUID()` | the same reach without the `Java.type` step | The last row matters: a bare fully-qualified reference works as well as the explicit `Java.type(...)` form, which is why so many feature files reach `java.util.UUID` or `java.lang.Thread` without any setup at all. ## What a payload looks like on the Java side There is no object mapper in this seam and nothing to configure. A JSON object in a feature file is a map and a JSON array is a list, and they arrive in Java as exactly that: ```java package com.example.util; import java.util.List; import java.util.Map; public class Signer { public static String name(Map<String, Object> body) { return (String) body.get("name"); } public static int count(List<Map<String, Object>> items) { return items.size(); } } ``` So a helper signature is written against `Map<String, Object>` rather than against a model class. That is a genuine trade: you never write or maintain a DTO for a test payload, and you also never get a compiler error when the payload's shape changes. ## When to reach for Java, and when not to Reach for it when the logic is an **algorithm** whose correctness you want checked rather than merely exercised — a signature, a token derivation, a decoder, a date rule with edge cases. Those belong in a compiled, unit-tested class, and `Java.type` is the seam that lets a feature file use one without the feature file stopping being the test. Do not reach for it to hide the interaction. The moment a single step hides the whole request and its response behind one Java call, the feature file has stopped describing anything and the argument for choosing Karate at all has evaporated: what you have then is a Java suite with a feature-file wrapper, which is the arrangement Karate exists to avoid. ## How to answer this in an interview Open by refusing the premise of "adding a keyword" — the set is fixed and there is no registration point. Then name the two routes and say what each is for: JavaScript when the logic should stay visible beside the scenario, `Java.type` when you want the compiler and unit tests around it. Land the interop detail that shows you have done it: JSON crosses as a plain `Map` or `List`, so helpers are written against collections and no mapping layer exists to configure. Finish with the boundary — interactions in the feature file, algorithms behind Java — because that is the judgement the question is really probing.
- Is there any way at all to teach Karate a new step keyword?No. The keyword table is built from Karate's own source and there is no service loader, annotation or scanned directory that lets you contribute to it. Anything the table does not cover has to be expressed as an expression on an existing keyword — usually `def`, `eval` or `call`.
- Do you need `Java.type` to reach a JDK class?Not for the common cases. A bare fully-qualified reference such as `java.util.UUID.randomUUID()` or `java.lang.Thread.sleep(100)` resolves on its own inside an expression. `Java.type` is the explicit form, and it reads better when you want the class bound to a named variable for reuse across a scenario.
- Why write a helper against `Map<String, Object>` rather than a model class?Because that is what crosses the seam. A JSON object in a feature file is a map and an array is a list, and no object-mapping layer sits between them and Java. You could construct a model inside the helper, but the method Karate calls has to accept the collection it is handed.
saying these in an interview costs you the question
- Says you register custom step definitions to extend Karate's keywords
- Claims a Karate expression is a bespoke language rather than JavaScript
- Thinks a JSON body arrives in Java as a mapped model object
- Puts a whole scenario behind one Java call and calls it reuse
- Believes Java.type is required before any JDK class can be reached