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.
Below is the same order in three response formats: you can see what changes in the body and in the client code once the wrapper and the null fields are removed.
The order state is the same in all three steps — only the response format changes. Remove the success/data/error wrapper and the extra level disappears: the client reads r.orderId instead of r.data.orderId. Add non_null and only three of the eight body fields remain, none of them null. The empty items array stays: that is not “no data”, it is “present, no elements”.
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
Idsuffix:orderId,customerId,parentCategoryId. A bareidis unclear — an identifier of what? - Dates — a string in ISO 8601 format:
2026-05-26for a date,2026-05-26T10:30:00Zfor a point in time (withZfor UTC). No2026-05-26 10:30:00without theTletter — that is not the standard. - Collections — plural:
items,tags,errors. - Enum values —
UPPER_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": trueis pointless. - For errors there is a separate standard (RFC 9457) that describes the problem better.
- The client writes
response.data.orderIdinstead ofresponse.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
Idsuffix: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
Locationheader + 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": ... }. nullin a 2xx response is forbidden — if there is no data, the field is not in the JSON.nullin a PATCH body — a special "remove the field" command (JSON Merge Patch).- Empty collections —
[], notnull.
What to read next
- 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.