Documenting file uploads, pagination and examples in OpenAPI, @ApiResponse/@ApiOperation vs springdoc's @Operation, serving Swagger UI, hiding endpoints, annotations vs YAML-first specs, versioning OpenAPI docs, contract testing and conformance checks; handling validation exceptions, custom error formats, business exceptions with HTTP semantics, MethodArgumentNotValidException, @Valid vs @Validated, validation groups, nested constraint violations, not leaking internals; versioning strategies and preferences, backward compatibility, graceful deprecation, multiple versions with routes, content-negotiation versioning, detecting outdated clients, evolving without breaking consumers; and GraphQL — implementing, securing, and REST vs GraphQL.
Published September 25, 2026
API contracts are long-lived promises. Show:
@ApiResponse and @ApiOperation?Short answer:
springdoc-openapi (org.springdoc:springdoc-openapi-starter-webmvc-ui, or the -webflux-ui variant) is the current choice for Spring Boot 3. It scans the controllers at runtime, and serves:
/v3/api-docs (the OpenAPI 3 JSON or YAML);/swagger-ui.html.Configure it with springdoc.* properties, and group APIs with GroupedOpenApi. Springfox is dead (it doesn't support Boot 3).
The annotations:
@ApiOperation/@ApiResponse (with @ApiParam) are Swagger 2 / Springfox annotations (io.swagger.annotations);@Operation(summary, description), @ApiResponse(responseCode, description, content), @Parameter, @Schema, @Tag and @SecurityRequirement. They enrich the generated spec with descriptions, error responses, examples and security.@Operation(summary = "Create order", description = "Idempotent via Idempotency-Key header")
@ApiResponse(responseCode = "201", description = "Created")
@ApiResponse(responseCode = "409", description = "Duplicate idempotency key with different payload",
content = @Content(mediaType = "application/problem+json", schema = @Schema(implementation = ProblemDetail.class)))
@PostMapping("/orders")
ResponseEntity<OrderDto> create(@Valid @RequestBody CreateOrderRequest req, @RequestHeader("Idempotency-Key") String key) { ... }
Short answer:
requestBody with content: multipart/form-data, and a schema with type: string, format: binary for the file part, plus the other fields. In springdoc, use @RequestPart MultipartFile file (detected automatically), and @Schema for the metadata parts. Document the size limits and allowed types in the descriptions.page, size, sort, or cursor/limit), with defaults and maximums (@ParameterObject Pageable in springdoc);items, nextCursor, totalElements), or document the Link headers;#/components/schemas/PageOfOrderDto).@ExampleObjects on @Content (several named examples: a success and each error case), or example values on @Schema fields. In YAML-first specs, use examples: per media type. Keep the examples realistic and valid: they drive mocks and SDK documentation.Short answer:
@Hidden (on a controller or method) or @Operation(hidden = true);springdoc.paths-to-exclude=/internal/** and springdoc.packages-to-scan;GroupedOpenApi, which exposes only public groups;springdoc.api-docs.enabled=false and springdoc.swagger-ui.enabled=false, or protect them behind authentication.Hiding isn't securing. Internal endpoints still need authorisation and network restrictions.
Short answer:
Many organisations use design-first for public or shared APIs, and code-first for internal ones, with conformance checks either way.
Short answer:
openapi-v1.yaml, openapi-v2.yaml) with info.version set, served at separate URLs (/v3/api-docs/v1, through springdoc GroupedOpenApi per version path), and published in a developer portal with a changelog.deprecated: true on operations or fields, plus sunset dates;oasdiff breaking) compares against the last released spec;info.version tracks the document's own version (it can be semantic-versioned, with minor additive changes), separately from the major API version in the URL.Short answer:
openapi-spec-validator wrappers. That fails the test if a response has undocumented fields or wrong types or statuses.MethodArgumentNotValidException?Short answer:
@Valid @RequestBody → the RequestResponseBodyMethodProcessor validates → on failure it throws MethodArgumentNotValidException (with a BindingResult). For @RequestParam/@PathVariable constraints with method validation (Spring 6.1+), it's HandlerMethodValidationException; for @Validated services, ConstraintViolationException.ResponseEntityExceptionHandler (or Boot's default error handling) returns 400. With spring.mvc.problemdetails.enabled=true, you get a ProblemDetail body.@RestControllerAdvice (extending ResponseEntityExceptionHandler) that overrides handleMethodArgumentNotValid, and returns structured field errors, consistently, for all three exception types.Short answer: Use Problem Details, plus an errors array:
{
"type": "https://api.shop.example/problems/validation",
"title": "Validation failed",
"status": 400,
"detail": "2 fields are invalid",
"instance": "/api/orders",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{ "field": "lines[0].quantity", "code": "Positive", "message": "must be greater than 0", "rejectedValue": -1 },
{ "field": "shippingAddress.postcode", "code": "Pattern", "message": "invalid postcode" }
]
}
ResponseEntityExceptionHandler, setting ProblemDetail properties (setProperty("errors", …)), using the type URIs for documentation, and internationalising the messages (MessageSource, ValidationMessages.properties).rejectedValues (passwords, card numbers).Short answer:
InsufficientStockException, OrderAlreadyShippedException) with error codes, and with no HTTP knowledge.@RestControllerAdvice maps them to statuses: not found → 404, conflict or state → 409, business-rule violation → 422, forbidden action → 403, a dependency down → 503 (with Retry-After).type/code, a human detail, and actionable data (for example availableQuantity).HttpStatus-agnostic category, which the advice maps; or ErrorResponseException/ResponseStatusException for web-layer-specific errors.@Valid and @Validated?Short answer:
@Valid (Jakarta Bean Validation): triggers validation of the annotated argument or return value, and cascades into nested objects and collection elements. It has no groups.@Validated (Spring):
@Validated(OnCreate.class));MethodValidationPostProcessor), which is useful on services and configuration properties.Use @Valid on request bodies and nested fields, and @Validated when you need groups, or service or @ConfigurationProperties validation.
Short answer:
OnCreate, OnUpdate) on the constraints (@Null(groups = OnCreate.class) Long id, @NotNull(groups = OnUpdate.class) Long id), with the group chosen at the entry point: @Validated(OnCreate.class) @RequestBody OrderDto dto. @GroupSequence orders cheap validations before expensive ones. For complex conditions (field B required when A = X), use a class-level custom constraint, which is clearer than juggling groups. Many teams prefer separate request DTOs per operation (CreateOrderRequest, UpdateOrderRequest), avoiding groups altogether.@Valid on the nested field, or on the container elements (List<@Valid OrderLineDto> lines), so validation cascades. Violations report property paths (lines[1].quantity, address.postcode), which your error mapper exposes as the field. Without @Valid on the nested field, nested constraints are silently skipped.Short answer:
server.error.include-stacktrace=never, include-message=never (the defaults) and include-binding-errors carefully.Short answer:
/api/v1/orders. Explicit, easy to route, cache and document, and the most common. The purist criticism is that a URI should identify a resource, not a version.X-API-Version: 2 or API-Version. Clean URIs, but less visible, and harder to test in a browser and to cache (needs Vary).?version=2. Simple, but it mixes versioning into resource queries.Accept: application/vnd.shop.order.v2+json. It's RESTful (it versions the representation), and supports per-resource evolution, but it's complex for clients and tooling.@GetMapping(value = "/orders/{id}", produces = "application/vnd.shop.order.v1+json") and produces = "…v2+json" on two handler methods (or one handler plus version-specific serialisers). Return Content-Type accordingly, and add Vary: Accept for caches. Spring Framework 7 adds first-class API versioning support (configurable resolvers: header, path, query, media type).Short answer:
FAIL_ON_UNKNOWN_PROPERTIES=false), and servers accept old request shapes.Short answer:
Deprecation header (RFC 9745), the Sunset header (RFC 8594) with the removal date, and a Link to the migration guide. Mark the operations deprecated: true in OpenAPI.User-Agent), and gateway analytics. That's how you detect the outdated clients: tag the requests by version, and dashboard or alert on usage.Short answer:
Routes (yes): different request mappings per version in the same application:
/api/v1/... and /api/v2/... controllers, sharing the service layer, with version-specific DTOs and mappers;/v1/** to the legacy deployment, and /v2/** to the new one.This lets the versions coexist, and be deployed independently.
Profiles (no, as a versioning mechanism): profiles select configuration per deployment environment. A profile applies to the whole instance, so one instance can't serve v1 and v2 at the same time. You'd need separate deployments per version, which is better handled by explicit routing (and profiles make the version behaviour implicit and error-prone).
Short answer:
Versioning: GraphQL avoids many version bumps. Clients select exactly the fields they need, so adding fields never breaks anyone, and fields are deprecated in the schema (@deprecated(reason:)), with usage tracked per field. It's "continuous evolution", not a free pass: removing or changing fields is still breaking.
Implementation in Java: Spring for GraphQL (built on graphql-java):
schema.graphqls;@QueryMapping/@MutationMapping/@SchemaMapping controllers;@BatchMapping or DataLoader to solve N+1 when resolving nested fields;GraphQlTester.Netflix DGS is an alternative.
Securing GraphQL:
errors).REST vs GraphQL:
Many companies use REST (or gRPC) internally, and GraphQL at the edge.
Q: What is Spectral? A: An open-source linter for OpenAPI and AsyncAPI documents, with rule sets (naming, pagination, error formats, security definitions). Run it in CI to enforce API style-guide consistency across teams.
Q: How do you document authentication in OpenAPI?
A: Define components.securitySchemes (for example bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT} or OAuth2 flows with scopes), and apply security globally or per operation. In springdoc: @SecurityScheme and @SecurityRequirement.
Q: What is AsyncAPI? A: An OpenAPI-like specification for event-driven APIs (Kafka topics, AMQP, WebSockets): channels, messages, schemas and bindings. It's used for documenting and generating code for asynchronous contracts.
Q: How do you stop clients from depending on undocumented fields?
A: Return only the documented DTO fields (no entity serialisation), validate responses against the spec in tests, and use strict schemas (additionalProperties: false) in contract tests for your own outputs.