skip to content

In openapi-generator, what do you get from a server stub versus a client SDK for the same spec?

level: middleimportance: must knowfreq 62%

answer

  1. -g picks the side and the language
  2. Stub receives calls; SDK makes them
  3. Both share the model layer
  4. operationId names the method
  5. Tags decide the class it lands in

basics

~20 s

Both derive models from the spec's schemas, but a server generator emits routing or controller interfaces you implement, while a client generator emits a ready-to-call HTTP client. The generator is chosen with -g, and operationId and tags determine method and class names.

solid answer

~50 s

You run `openapi-generator-cli generate -i openapi.yaml -g <generator> -o <dir>`, and `-g` decides which side you get. A **server** generator produces the incoming edge: model classes plus interfaces or abstract controllers with routes, parameter binding and validation already wired — you implement the method bodies, and the generated code is deliberately not your business logic. A **client** generator produces the outgoing edge: the same models plus a class per tag whose methods build the request, serialise the body, send it and deserialise the response. Both derive names from the document: `operationId` becomes the method name (and a missing one gets a synthesised name from the verb and path), the first `tag` decides which API class an operation lands in, untagged operations fall into a default one, and `components.schemas` entries become model classes while inline schemas get synthesised names.

code

bash · 12 lines
bash
# server side: interfaces you implement
openapi-generator-cli generate \
  -i specs/orders-v1.yaml \
  -g spring \
  -o build/generated/orders-api \
  --additional-properties=interfaceOnly=true

# client side: a callable SDK from the same document
openapi-generator-cli generate \
  -i specs/orders-v1.yaml \
  -g typescript-fetch \
  -o build/generated/orders-client

go deeper

for a junior

Know the command shape and the split: -g picks the generator, a server stub gives you interfaces to implement, a client SDK gives you methods to call.

for a middle

Explain how the document drives the output — operationId to method names, first tag to API class, named components to model classes, response schema to return type.

for a senior

Show how you keep the boundary safe: implementation separate from generated code, contract changes surfacing as compile errors, and generation run in CI to catch spec problems early.

for a principal

Own the spec conventions that make generation viable across an organisation — mandatory operation ids, a tag taxonomy, named components — and which targets are generated as a supported deliverable.

## One document, two edges A generator turns the contract into code for whichever side of the wire you are on. The command is the same either way — `openapi-generator-cli generate -i openapi.yaml -g <generator> -o out` — and the `-g` value selects a *generator*, of which there are many per language (for example `spring` and `kotlin-spring` on the server side, `java`, `typescript-fetch`, `typescript-axios`, `python` and `go` on the client side). ## What a server generator emits A server stub is the plumbing between an HTTP request and your code: - **Model classes** for the schemas, with serialisation annotations and, where the target supports it, validation constraints derived from `required`, `maxLength`, `minimum` and friends. - **API interfaces or abstract controllers**, one per tag, with a method per operation carrying the route, the HTTP method, and parameters bound from path, query, header and body. What it does *not* emit is behaviour. The generated method either throws "not implemented" or delegates; you supply the implementation. This matters because it fixes the boundary: regeneration replaces the stub and must never replace your logic, so your code has to live in a separate class that implements the generated interface, not in edits to the generated file. Most server generators expose options for exactly this. `--additional-properties=interfaceOnly=true` on the `spring` generator emits interfaces without a framework wiring layer, so your own controller implements the interface; `delegatePattern=true` generates a controller that forwards to a delegate interface you implement. Either way you get compile-time enforcement: change the spec, regenerate, and the build breaks wherever the contract moved. ## What a client generator emits A client SDK is the mirror image: - The **same model classes** derived from the same schemas. - An **API class per tag** whose methods take typed parameters, build the URL and query string, serialise the request body, apply configured authentication, execute the call and deserialise the response into a model — throwing or returning an error type on non-success statuses. - Supporting infrastructure: a configuration object for base URL, timeouts and credentials, and serialisation setup. Many client generators offer a `library` option to select the underlying HTTP stack, which is worth setting deliberately rather than accepting a default your project does not otherwise use. ## How the document shapes the output This is the part interviewers probe, because it explains why generated code is sometimes unusable. - **`operationId` → method name.** A clean `listOrders` gives `listOrders(...)`. Omit it and the generator synthesises a name from the HTTP method and path, producing things like `ordersOrderIdItemsGet`. Every operation should have a hand-written, unique `operationId`. - **`tags` → class grouping.** Operations are grouped by their first tag into an API class named after it; an operation with no tag lands in a default API class. Tagging is therefore a code-structure decision, not just a docs-navigation one. - **`components.schemas` → model names.** A named component becomes a named class. An inline schema gets a synthesised name derived from the operation and property path — unstable across edits and meaningless to consumers. If you want a first-class model in the SDK, name it as a component. - **Responses → return types.** The success response's schema becomes the return type; how non-2xx responses map to exceptions or result types varies by generator. - **`required` and `nullable` → nullability.** Sloppy use here produces a client where everything is optional and callers null-check constantly. ## Where generated code disappoints Support for advanced schema composition is uneven across generators — polymorphic unions in particular map differently, or awkwardly, depending on the target language. Free-form objects tend to degrade to a map type. And because everything flows from names in the document, an inconsistent spec produces an inconsistent SDK: mixed tag naming yields oddly named classes, missing `operationId`s yield unreadable methods. The practical conclusion is that a spec intended for codegen has extra requirements beyond validity — unique operation ids, deliberate tags, named components, honest required/nullable — and the cheapest way to find violations is to generate for at least one target in CI and let compilation fail.

  • Why should every operation carry a hand-written operationId?
    Because it becomes the generated method name. Without one the generator synthesises a name from the HTTP method and path, giving unreadable identifiers like `ordersOrderIdItemsGet` that also change whenever the path changes — a silent breaking change for anyone who has written code against the SDK.
  • How do tags affect generated code?
    Operations are grouped by their first tag into one API class per tag, and untagged operations land in a default catch-all class. Tagging is therefore a structural decision about the SDK, not only a documentation grouping — inconsistent or missing tags produce oddly named or overloaded API classes.
  • You regenerate a server stub and your implementation stops compiling. Is that a problem?
    No, that is the mechanism working. The generated interface encodes the contract, so a spec change surfaces as a compile error exactly where your code no longer satisfies it. The failure mode to fear is the opposite: a change that regenerates cleanly and drifts silently, which is why implementations should implement generated interfaces rather than merely resemble them.

saying these in an interview costs you the question

  • Thinks a server stub contains business logic
  • Ships without operationIds and accepts synthesised names
  • Implements logic by editing generated files
  • Assumes every generator handles polymorphic schemas identically
  • Ignores tags because they are just docs grouping

context