Java SDK
What is the Java SDK?
The Java SDK provides contract validation for Java applications with Spring integration. It includes HTTP client adapters for automatic validation with OkHttp, producer middleware for Spring and Servlet applications, and a test kit for schema compliance testing.
For information about SDK design patterns, adapter architecture, and cross-language consistency, see SDK Architecture.
Installation
Maven Central (Recommended)
No authentication required.
Maven
<dependency>
<groupId>io.github.sahina</groupId>
<artifactId>cvt-sdk</artifactId>
<version>0.1.0</version>
</dependency>
Gradle
dependencies {
implementation("io.github.sahina:cvt-sdk:0.1.0")
}
Quick Start
import io.github.sahina.sdk.ContractValidator;
import io.github.sahina.sdk.ValidationResult;
import io.github.sahina.sdk.ValidationRequest;
import io.github.sahina.sdk.ValidationResponse;
public class Example {
public static void main(String[] args) throws Exception {
ContractValidator validator = new ContractValidator("localhost:9550");
// Register a schema from file
validator.registerSchema("petstore", "./openapi.json");
// Or register from URL:
// validator.registerSchema("petstore", "https://petstore3.swagger.io/api/v3/openapi.json");
// Validate an interaction
ValidationResult result = validator.validate(
ValidationRequest.builder().method("GET").path("/pet/123").build(),
ValidationResponse.builder().statusCode(200)
.body("{\"id\": 123, \"name\": \"doggie\", \"status\": \"available\"}").build()
);
System.out.println("Valid: " + result.isValid());
if (!result.isValid()) {
System.out.println("Errors: " + result.getErrors());
}
validator.close();
}
}
API Reference
ContractValidator
Constructor
// Simple usage (insecure connection)
ContractValidator() // Uses localhost:9550
ContractValidator(String address)
// With builder pattern (TLS and API key)
ContractValidator.builder()
.address("localhost:9550")
.tlsEnabled(true)
.rootCertPath("./certs/ca.crt")
.apiKey("your-api-key")
.build()
Simple usage:
ContractValidator validator = new ContractValidator("localhost:9550");
With builder:
ContractValidator validator = ContractValidator.builder()
.address("localhost:9550")
.tlsEnabled(true)
.rootCertPath("./certs/ca.crt")
.apiKey("your-api-key")
.build();
| Option | Type | Description |
|---|---|---|
address | String | Server address (default: localhost:9550) |
tlsEnabled | boolean | Enable TLS |
rootCertPath | String | Path to CA certificate |
apiKey | String | API key for authentication |
Methods
registerSchema
Registers an OpenAPI schema from a file path or URL.
void registerSchema(String schemaId, String schemaPath) throws IOException
// From local file
validator.registerSchema("petstore", "./openapi.json");
// From URL
validator.registerSchema("petstore", "https://petstore3.swagger.io/api/v3/openapi.json");
registerSchemaWithVersion
Registers a schema with version information for comparison.
void registerSchemaWithVersion(String schemaId, String schemaPath, String version) throws IOException
validator.registerSchemaWithVersion("petstore", "./openapi.json", "1.0.0");
validate
Validates an HTTP request/response pair against the registered schema.
ValidationResult validate(ValidationRequest request, ValidationResponse response)
ValidationResult result = validator.validate(
ValidationRequest.builder().method("GET").path("/pet/123").build(),
ValidationResponse.builder().statusCode(200)
.body("{\"id\": 123, \"name\": \"doggie\"}").build()
);
if (!result.isValid()) {
System.err.println("Errors: " + result.getErrors());
}
compareSchemas
Compares two schema versions for breaking changes.
CompareResult compareSchemas(String schemaId, String oldVersion, String newVersion)
CompareResult result = validator.compareSchemas("petstore", "1.0.0", "2.0.0");
if (!result.isCompatible()) {
for (BreakingChange change : result.getBreakingChanges()) {
System.out.println(change.getType() + ": " + change.getDescription());
}
}
generateFixture
Generates test fixtures from the schema.
GeneratedFixture generateFixture(String method, String path, GenerateOptions options)
GeneratedFixture fixture = validator.generateFixture("GET", "/pet/{petId}", null);
System.out.println("Request: " + fixture.getRequest());
System.out.println("Response: " + fixture.getResponse());
generateResponse
Generates a response fixture only.
GeneratedResponse generateResponse(String method, String path, GenerateOptions options)
generateRequestBody
Generates a request body fixture for an endpoint.
Object generateRequestBody(String method, String path, GenerateOptions options)
Object body = validator.generateRequestBody("POST", "/pet", null);
System.out.println("Request body: " + body);
listEndpoints
Lists all endpoints in the registered schema.
List<EndpointInfo> listEndpoints()
List<EndpointInfo> endpoints = validator.listEndpoints();
for (EndpointInfo ep : endpoints) {
System.out.println(ep.getMethod() + " " + ep.getPath() + " - " + ep.getSummary());
}
registerConsumer
Registers a consumer with expected interactions.
ConsumerInfo registerConsumer(RegisterConsumerOptions options)
ConsumerInfo consumer = validator.registerConsumer(
RegisterConsumerOptions.builder()
.consumerId("order-service")
.consumerVersion("2.1.0")
.schemaId("petstore")
.schemaVersion("1.0.0")
.environment("prod")
.usedEndpoints(List.of(
new EndpointUsage("GET", "/pet/{petId}", List.of("id", "name", "status"))))
.build()
);
listConsumers
Lists all consumers for a schema.
List<ConsumerInfo> listConsumers(String schemaId, String environment)
List<ConsumerInfo> consumers = validator.listConsumers("petstore", "prod");
for (ConsumerInfo c : consumers) {
System.out.println(c.getConsumerId() + " v" + c.getConsumerVersion());
}
deregisterConsumer
Removes a consumer registration.
void deregisterConsumer(String consumerId, String schemaId, String environment)
canIDeploy
Checks if a schema version can be safely deployed.
CanIDeployResult canIDeploy(String schemaId, String newVersion, String environment)
CanIDeployResult result = validator.canIDeploy("petstore", "2.0.0", "prod");
if (!result.isSafeToDeploy()) {
System.out.println("Unsafe: " + result.getSummary());
for (ConsumerImpact c : result.getAffectedConsumers()) {
System.out.println("- " + c.getConsumerId() + " will break");
}
}
close
Closes the gRPC connection. Implements AutoCloseable for try-with-resources.
void close()
HTTP Adapters
OkHttp Adapter
Automatically validate all OkHttp requests:
import io.github.sahina.sdk.adapters.OkHttpContractAdapter;
import io.github.sahina.sdk.adapters.AdapterConfig;
ContractValidator validator = new ContractValidator("localhost:9550");
validator.registerSchema("petstore", "./openapi.json");
OkHttpContractAdapter adapter = new OkHttpContractAdapter(validator)
.withConfig(AdapterConfig.builder()
.autoValidate(true)
.build());
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(adapter)
.build();
// All requests are now validated
Request request = new Request.Builder()
.url("http://petstore-service/pet/123")
.build();
Response response = client.newCall(request).execute();
// Check captured interactions
List<CapturedInteraction> interactions = adapter.getInteractions();
Producer Middleware
Spring Interceptor
import io.github.sahina.sdk.producer.ProducerConfig;
import io.github.sahina.sdk.producer.ValidationMode;
import io.github.sahina.sdk.producer.adapters.SpringInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ContractValidator validator;
@Override
public void addInterceptors(InterceptorRegistry registry) {
ProducerConfig config = ProducerConfig.builder()
.schemaId("petstore")
.validator(validator)
.mode(ValidationMode.STRICT) // STRICT | WARN | SHADOW
.build();
registry.addInterceptor(new SpringInterceptor(config))
.addPathPatterns("/api/**")
.excludePathPatterns("/health", "/metrics");
}
}
Servlet Filter
import io.github.sahina.sdk.producer.adapters.ServletFilter;
@WebFilter(urlPatterns = "/api/*")
public class ValidationFilter extends ServletFilter {
public ValidationFilter(ContractValidator validator) {
super(ProducerConfig.builder()
.schemaId("petstore")
.validator(validator)
.mode(ValidationMode.STRICT)
.build());
}
}
Producer Test Kit
Test your API responses against your schema without real consumers:
import io.github.sahina.sdk.producer.ProducerTestKit;
import io.github.sahina.sdk.producer.TestResponseData;
import io.github.sahina.sdk.producer.TestValidationResult;
import org.junit.jupiter.api.*;
public class PetHandlerTest {
private ProducerTestKit testKit;
@BeforeEach
void setup() {
testKit = ProducerTestKit.builder()
.schemaId("petstore")
.serverAddress("localhost:9550")
.build();
}
@AfterEach
void teardown() {
testKit.close();
}
@Test
void getPetReturnsValidResponse() {
// Call your handler
Pet pet = petHandler.getPet(123);
// Validate
TestValidationResult result = testKit.validateResponse(
"GET",
"/pet/123",
TestResponseData.builder()
.statusCode(200)
.body(pet)
.build()
);
assertTrue(result.isValid());
assertTrue(result.getErrors().isEmpty());
}
@Test
void detectsInvalidResponse() {
TestValidationResult result = testKit.validateResponse(
"GET",
"/pet/123",
TestResponseData.builder()
.statusCode(200)
.body(Map.of("id", "not-a-number")) // Invalid type
.build()
);
assertFalse(result.isValid());
assertFalse(result.getErrors().isEmpty());
}
}
ForEndpoint Helper
Test multiple scenarios for the same endpoint:
ProducerTestKit.EndpointTester getPetEndpoint = testKit.forEndpoint("GET", "/pet/{petId}");
// Test valid response
TestValidationResult result = getPetEndpoint.validateResponse(
TestResponseData.builder()
.statusCode(200)
.body(Map.of("id", 123, "name", "doggie", "status", "available"))
.build(),
Map.of("petId", "123")
);
// Test not found
result = getPetEndpoint.validateResponse(
TestResponseData.builder()
.statusCode(404)
.body(Map.of("message", "Pet not found"))
.build(),
Map.of("petId", "999")
);
Auto-Registration
Build consumer info from captured interactions:
import io.github.sahina.sdk.AutoRegisterConfig;
import io.github.sahina.sdk.adapters.CapturedInteraction;
// From OkHttp adapter
OkHttpContractAdapter adapter = ...;
List<CapturedInteraction> interactions = adapter.getInteractions();
// Auto-register consumer from captured interactions
ConsumerInfo info = validator.registerConsumerFromInteractions(
interactions,
AutoRegisterConfig.builder()
.consumerId("order-service")
.consumerVersion("2.1.0")
.environment("dev")
.schemaVersion("1.0.0")
.build()
);
System.out.println("Registered: " + info.getConsumerId());
Or preview before registering:
AutoRegisterUtils.BuildResult result = validator.buildConsumerFromInteractions(
interactions,
AutoRegisterConfig.builder()
.consumerId("order-service")
.consumerVersion("2.1.0")
.environment("dev")
.schemaVersion("1.0.0")
.build()
);
if (result.hasError()) {
System.err.println("Error: " + result.getError());
} else {
System.out.println("Would register: " + result.getOptions().getUsedEndpoints().size() + " endpoints");
}
TLS Configuration
ContractValidator validator = ContractValidator.builder()
.address("localhost:9550")
.tlsEnabled(true)
.rootCertPath("./certs/ca.crt")
.build();
API Key Authentication
ContractValidator validator = ContractValidator.builder()
.address("localhost:9550")
.apiKey("your-api-key")
.build();
Types
public class ValidationRequest {
private String method;
private String path;
private Map<String, String> headers;
private String body;
}
public class ValidationResponse {
private int statusCode;
private Map<String, String> headers;
private String body;
}
public class ValidationResult {
private boolean valid;
private List<String> errors;
}
public class BreakingChange {
private String type;
private String path;
private String method;
private String description;
private String oldValue;
private String newValue;
}
public class CompareResult {
private boolean compatible;
private List<BreakingChange> breakingChanges;
}
public class ConsumerInfo {
private String consumerId;
private String consumerVersion;
private String schemaId;
private String schemaVersion;
private String environment;
private long registeredAt;
private long lastValidatedAt;
private List<EndpointUsage> usedEndpoints;
}
public class GenerateOptions {
private int statusCode;
private boolean useExamples;
private String contentType;
}
public class GeneratedRequest {
private String method;
private String path;
private Map<String, String> headers;
private Object body;
}
public class GeneratedResponse {
private int statusCode;
private Map<String, String> headers;
private Object body;
}
public class GeneratedFixture {
private GeneratedRequest request;
private GeneratedResponse response;
}
public class EndpointInfo {
private String method;
private String path;
private String summary;
}
public class EndpointUsage {
private String method;
private String path;
private List<String> usedFields;
}
public class RegisterConsumerOptions {
private String consumerId;
private String consumerVersion;
private String schemaId;
private String schemaVersion;
private String environment;
private List<EndpointUsage> usedEndpoints;
}
public class ConsumerImpact {
private String consumerId;
private String consumerVersion;
private String currentSchemaVersion;
private String environment;
private boolean willBreak;
private List<BreakingChange> relevantChanges;
}
public class CanIDeployResult {
private boolean safeToDeploy;
private String summary;
private List<BreakingChange> breakingChanges;
private List<ConsumerImpact> affectedConsumers;
}
Error Handling
try {
validator.registerSchema("petstore", "./openapi.json");
} catch (IOException e) {
System.err.println("Failed to read or register schema: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("Schema registration failed: " + e.getMessage());
}
// Validation errors are returned in the result, not thrown
ValidationResult result = validator.validate(request, response);
if (!result.isValid()) {
System.err.println("Validation errors: " + result.getErrors());
}
Try-with-resources
The ContractValidator implements AutoCloseable:
try (ContractValidator validator = new ContractValidator("localhost:9550")) {
validator.registerSchema("petstore", "./openapi.json");
ValidationResult result = validator.validate(request, response);
}
// Connection automatically closed
Related Documentation
- Consumer Testing Guide - Testing your API integrations
- Producer Testing Guide - Validating your APIs
- API Reference - Full gRPC API documentation