← Back to the section

When an API returns data, the client expects a predictable structure: clear field names, readable dates, and definite rules for missing values. Without conventions, every team invents its own format — and integration turns into a quest to guess what null means in a given field.

In this article we go through concrete rules: how to name fields, how to return dates, when to return 201 and when 204, and why null in a response is a problem.

Field names: camelCase, not snake_case

Two camps have formed on the web: some APIs use created_at and order_id, others use createdAt and orderId. For a JSON API on Java the right choice is camelCase, because:

  • JavaScript (the main consumer of REST APIs) uses camelCase by default;
  • Jackson (the standard JSON library in Spring) writes field names as they appear in Java by default — and Java uses camelCase.

A good example of a JSON response:

{
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "totalAmount": 1500.00,
  "status": "IN_PROGRESS",
  "createdAt": "2026-05-26T10:30:00Z",
  "deliveryAddress": {
    "streetName": "Lenina",
    "zipCode": "123456"
  },
  "items": [
    { "itemId": "abc123", "productName": "Keyboard", "quantity": 2 }
  ]
}

A few naming rules:

  • Identifiers — with an Id suffix: orderId, customerId, parentCategoryId. A bare id is unclear — an identifier of what?
  • Dates — a string in ISO 8601 format: 2026-05-26 for a date, 2026-05-26T10:30:00Z for a point in time (with Z for UTC). No 2026-05-26 10:30:00 without the T letter — that is not the standard.
  • Collections — plural: items, tags, errors.
  • Enum valuesUPPER_SNAKE_CASE: IN_PROGRESS, CREDIT_CARD, OUT_OF_STOCK. The client immediately sees that it is an enumeration.

Boolean fields

There is no strict requirement to write isActive or active — both variants occur. Only one thing matters: consistency within the project. If you chose active/enabled — use it everywhere; if isActive/isEnabled — also everywhere.

{
  "active": true,
  "hasDiscount": false,
  "canCancel": true
}

Dates and time: ISO 8601

Always use ISO 8601. It is an international standard that every library in every language understands.

  • Date: "2026-05-26"
  • Date and time (UTC): "2026-05-26T10:30:00Z"
  • Date and time with offset: "2026-05-26T13:30:00+03:00"

A common mistake is passing a Unix timestamp as a number (1716720600). It is machine-readable, but inconvenient when debugging and not obvious to the client. An ISO 8601 string is human-readable and at the same time parses just as well.

The response format depends on the operation

Different operations — different response codes and different body structures.

Create (POST) — 201 + Location + body

When a resource is created, the server returns status 201 Created and a Location header with a link to the created resource. The body contains the created object itself:

HTTP/1.1 201 Created
Location: /api/v1/orders/550e8400-e29b-41d4-a716-446655440000

{
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "CREATED",
  "totalAmount": 0,
  "createdAt": "2026-05-26T10:30:00Z"
}

Why Location? The client immediately knows the URL of the new resource, without having to guess or construct it.

Update (PUT/PATCH) — 200 + updated resource

After an update we return the current state of the object:

HTTP/1.1 200 OK

{
  "orderId": "550e8400-...",
  "status": "CONFIRMED",
  "totalAmount": 1500.00,
  "updatedAt": "2026-05-26T11:00:00Z"
}

The client immediately sees what changed — without an additional GET request.

Delete (DELETE) — 204 No Content

A delete returns nothing. Status 204 No Content, empty body:

HTTP/1.1 204 No Content

There is no need to return { "success": true } — status 204 already signals success.

Action on a resource (action) — 200 + result

If the endpoint is an action (confirm an order, block a user), we return the updated resource:

POST /api/v1/orders/550e8400-.../confirm

HTTP/1.1 200 OK

{
  "orderId": "550e8400-...",
  "status": "CONFIRMED",
  "confirmedAt": "2026-05-26T11:00:00Z"
}

Single resource — a flat object

No wrappers. Just the object:

{
  "orderId": "550e8400-...",
  "status": "CONFIRMED",
  "totalAmount": 1500.00,
  "createdAt": "2026-05-26T10:30:00Z"
}

Nested objects and arrays inside a resource are fine. But do not wrap the resource in { "data": ..., "success": true } — that is an antipattern, more on it below.

Collection — content + pagination metadata

When you return a paginated list, the structure is as follows:

{
  "content": [
    { "orderId": "..." },
    { "orderId": "..." }
  ],
  "page": 1,
  "size": 20,
  "totalElements": 243,
  "totalPages": 13
}

The content field is the data itself, alongside it the pagination metadata. This is not a "wrapper" in the bad sense, it is the structure of a page.

null in a response — why it is a problem

When a client receives null in a field, it does not know what it means: is there no data at all? Not loaded yet? Was there, but deleted? Every null value is uncertainty.

The rule is simple: if a field has no value — it should not be in the JSON at all, rather than "discount": null.

Bad:

{
  "orderId": "...",
  "discount": null,
  "comment": null
}

Good:

{
  "orderId": "...",
  "status": "CONFIRMED"
}

More upsides: less traffic, simpler client code (if (data.discount) instead of if (data.discount !== null && data.discount !== undefined)).

In Spring this is configured with a single line — we tell Jackson not to include null fields:

@Bean
public ObjectMapper objectMapper() {
    return Jackson2ObjectMapperBuilder.json()
        .serializationInclusion(JsonInclude.Include.NON_NULL)
        .build();
}

The same logic applies to empty strings: "" is not "no data", it is "there is data, but it is empty". If there is no data — the field is not in the JSON.

null in a PATCH request body — a different story

In PATCH requests null has a special meaning according to the JSON Merge Patch standard (RFC 7396): it is a command to remove the field.

PATCH /api/v1/orders/550e8400-...
Content-Type: application/merge-patch+json

{ "comment": null }

This says: "remove the comment field from the resource". This is request semantics, not a violation of the rule about null in responses.

Envelope — an antipattern

Sometimes you see a response format like this:

{
  "success": true,
  "data": {
    "orderId": "...",
    "status": "CREATED"
  },
  "error": null
}

This is called an envelope (a "wrapper"). It seems convenient — always the same structure. But in practice it is an extra layer with no benefit:

  • The HTTP status (200, 404, 500) already reports whether it was a success or an error — duplicating it in "success": true is pointless.
  • For errors there is a separate standard (RFC 9457) that describes the problem better.
  • The client writes response.data.orderId instead of response.orderId — an extra level.

The right way: a single resource is returned as a flat object, a collection — via { "content": [...] } with pagination.

Empty collections — [] not null

If a collection is empty — return an empty array, not null and not a missing field:

{ "items": [] }

[] means "the collection exists, there are no elements". null or a missing field is ambiguous: is the collection empty, does it not exist, or is it not loaded?

In short

  • Field names — camelCase: orderId, totalAmount, createdAt.
  • Identifiers — an Id suffix: orderId, customerId.
  • Dates — a string in ISO 8601: 2026-05-26T10:30:00Z.
  • Enums — UPPER_SNAKE_CASE: IN_PROGRESS, CREDIT_CARD.
  • Create — 201 Created + a Location header + the resource body.
  • Update — 200 OK + the updated resource.
  • Delete — 204 No Content, empty body.
  • Single resource — a flat object, no wrappers.
  • Paginated collection — { "content": [...], "page": ..., "totalElements": ... }.
  • null in a 2xx response is forbidden — if there is no data, the field is not in the JSON.
  • null in a PATCH body — a special "remove the field" command (JSON Merge Patch).
  • Empty collections — [], not null.
  • URL and Resources in REST API — HTTP methods, status codes, URL structure.
  • Query Parameters and Pagination — how to pass filters and get pages.
  • Errors and the Problem Response — the RFC 9457 error format.
  • Headers and Tracing — Location, ETag and service headers.