API Contracts
API contracts define the observable behavior that clients can rely on when interacting with a service. They specify inputs, outputs, errors, invariants, and compatibility expectations so independently developed components can interoperate predictably.
An API contract describes what an endpoint promises rather than how its implementation works. For an HTTP API, this commonly includes the method and path, request parameters or body, response structure, status codes, error format, authentication requirements, and relevant headers.
A useful contract also captures behavioral rules. These can include validation constraints, required fields, default values, ordering guarantees, idempotency, pagination semantics, and invariants such as a resource identifier remaining stable. These rules matter because clients often depend on behavior that is not obvious from a type definition alone.
In Java backend systems, contracts may be represented with OpenAPI documents, DTOs, validation annotations, controller behavior, automated contract tests, or combinations of these. The implementation should satisfy the documented contract, while clients should avoid depending on unspecified behavior.
Compatibility is part of the contract. Adding an optional response field is often less disruptive than removing or changing an existing field, while changing a field's type, semantics, requiredness, or error behavior can break consumers. When a breaking change is necessary, teams commonly use explicit versioning or a migration strategy.
A strong contract separates stable external behavior from internal implementation details. Database schemas, private helper methods, and internal class structure generally are not API guarantees unless they become observable to consumers.
Key points
- Define observable inputs, outputs, errors, and behavioral guarantees.
- Document validation, defaults, invariants, and semantics—not only data shapes.
- Keep implementation details out of the external contract.
- Treat compatibility as a first-class concern when changing an API.
- Use OpenAPI and contract tests to make expectations explicit and verifiable.
Interview questions
What is an API contract, and what should it contain?
An API contract defines the behavior that a client can rely on when calling a service. For an HTTP endpoint, that includes the method, URL, request data, response data, status codes, and error behavior. It can also specify validation rules, authentication requirements, defaults, and important behavioral guarantees. The goal is to let clients use the API without knowing its internal implementation.
How do you maintain backward compatibility when changing an API?
First, I identify which consumers depend on the existing behavior and determine whether the proposed change is additive or breaking. I prefer compatible changes such as adding optional fields rather than removing fields or changing existing semantics. For unavoidable breaking changes, I would use a migration plan, explicit versioning where appropriate, and tests or documentation that let consumers transition safely. Compatibility decisions should consider both the data schema and behavioral semantics.
What is the difference between an API contract and an OpenAPI specification?
OpenAPI is a machine-readable format for describing many aspects of an HTTP API. An API contract is the broader set of guarantees made to consumers, including behavioral rules and compatibility expectations. OpenAPI can describe schemas, parameters, responses, authentication, and status codes very effectively, but teams may need additional documentation or tests for semantics such as idempotency or complex invariants. Therefore, an OpenAPI document can be a major artifact of the contract without necessarily being the entire contract.
How would you test an API contract in a Java backend?
I would test both structural and behavioral aspects of the contract. For structure, tests can validate request and response schemas, required fields, and status codes against an OpenAPI definition. For behavior, contract tests can exercise validation, error mapping, idempotency, and other documented guarantees. In a distributed system, consumer-driven contract testing can verify that provider changes continue to satisfy the expectations of important consumers.
A team wants to change an API error from a structured JSON object to a plain string. What would you consider?
I would treat the existing error representation as part of the observable contract if clients consume it. Before changing it, I would inspect consumer usage, document the intended new format, and determine whether the change is breaking. A compatibility approach could temporarily support both formats or introduce a new version or endpoint, depending on the system's constraints. I would also add contract tests so the intended error behavior remains explicit.