Tales from the Terminal — Season 1 — Episode 1
Episode 1 — The Case of the Vanishing Timezone: Director’s Cut
Tales from the Terminal — Season 1 — Episode 1
Episode 1 — The Case of the Vanishing Timezone: Director’s Cut
TL;DR
- Postman worked because it omitted the timezone flag; the Feign/Jackson client sent it as
null. - The server didn’t handle
null, causing a crash. - If you don’t need three possible states, use a primitive
booleanwith a default. It’s safer. - From a caller’s perspective, sending
nullwhen unsure is reasonable; the server should guard or set defaults. - Serialization matters: an absent field isn’t the same as a
nullfield. Postman and Jackson behave differently by default.
The Story
As the Feign client, you ran into a FeignException. Debugging traced the failure to the request body.
In Postman, the call worked perfectly:
curl --location 'https://***.example.com/getVehicleEventsByTrip?foid=35083&checkTimeZone=false' \
--header 'Content-Type: application/json' \
--data '{"deviceId":697163,"startTimestamp":"2025-07-18 08:12:27","endTimestamp":"2025-07-19 06:55:02"}'
Notice there is no timezone flag in the JSON body, yet it worked fine.
In code, the request DTO looked like this:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeviceEventRequestDTO {
private Integer deviceId;
private Integer sessionId;
private String startTimestamp;
private String endTimestamp;
private Integer recordLimit;
private Boolean applyTimeZoneOffset = true;
}
At one point, the request body was created with null for applyTimeZoneOffset:
DeviceEventRequestDTO body =
new DeviceEventRequestDTO(
requestDTO.getDeviceId(),
null,
requestDTO.getStartTimestamp(),
requestDTO.getEndTimestamp(),
requestDTO.getRecordLimit(),
null // null overwrote the default
);
The server received null, failed to guard against it, and crashed.
Who’s Responsible?
Caller’s perspective: Sending null when unsure of a field’s semantics is cautious and reasonable. A client shouldn’t have to inspect server code to avoid breaking it.
Server’s perspective: Robust servers should either apply safe defaults or explicitly reject null values. If a field must never be null, use a primitive type or validate input.
Bottom line: The client acted reasonably. The server is responsible for handling null safely.
Why Postman Worked but Feign Did Not
- Postman: Sends exactly what you type. If a field is missing, it’s completely absent.
- Feign/Jackson: Serializes DTOs. If a field is
null, it includes"field": nullby default. - On the server:
— When a field is absent, the default initializer (
= true) remains untouched. – Whennullis sent explicitly, it overwrites the default. If the code assumes a non‑null value, it fails.
Design Choices for the Timezone Flag
If three states aren’t needed (just true/false):
- Use a primitive boolean. This avoids
nullentirely:@Data @NoArgsConstructor @Builder public class DeviceEventRequestDTO { private Integer deviceId; private Integer sessionId; private String startTimestamp; private String endTimestamp; private Integer recordLimit; @Builder.Default private boolean applyTimeZoneOffset = true; }
@Data
@NoArgsConstructor
@Builder
public class DeviceEventRequestDTO {
private Integer deviceId;
private Integer sessionId;
private String startTimestamp;
private String endTimestamp;
private Integer recordLimit;
@Builder.Default
private boolean applyTimeZoneOffset = true;
}
- Hardening options:
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
- and add custom validation in setters or after binding.
If three states are needed (true/false/unspecified):
- Keep
Booleanbut preserve defaults whennullis sent:
@Data
@NoArgsConstructor
@Builder
public class DeviceEventRequestDTO {
private Integer deviceId;
private Integer sessionId;
private String startTimestamp;
private String endTimestamp;
private Integer recordLimit;
@Builder.Default
private Boolean applyTimeZoneOffset = true;
@JsonSetter(value = "applyTimeZoneOffset", nulls = Nulls.SKIP)
public void setApplyTimeZoneOffset(Boolean value) {
this.applyTimeZoneOffset = value; // default remains if null
}
}
This way, absent fields keep defaults and explicit nulls do not erase them.
Client‑Side Hygiene (Feign/Jackson)
- Omit nulls so that
"field": nullis never sent:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DeviceEventRequestDTO { ... }
- Or configure globally:
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- Enable Feign logging to confirm the payload:
feign:
client:
config:
default:
loggerLevel: FULL
Serialization Cheat Sheet
Scenario Client behavior Server implication Absent field Postman: field omitted; Jackson can omit via NON_NULL Default values or optional logic can apply. Null field Jackson (client): includes "field": null Server receives explicit null; defaults get overwritten, so validation is required.
Key Takeaways
- Absent ≠ null. This difference has real effects during serialization and deserialization.
- DTO design defines the contract. If a field must never be
null, use a primitive type or validate. - Caller sending
nullis not wrong. Robust servers must handle null values gracefully.
Recommendations
On the server:
- If three states aren’t needed, switch to a primitive boolean and reject
nulls. - If three states are needed, keep
Boolean, use@JsonSetter(nulls = Nulls.SKIP), and add validation.
On the client:
- Configure Jackson/Feign to omit null fields.
- Use Feign logging to verify payloads against Postman.
Conclusion
Once the timezone flag stopped being sent as null, Feign behaved exactly like Postman. The real issue wasn’t Feign versus Postman—it was the difference between absent and explicit null, and how DTO defaults can silently disappear. This case underscores the importance of serialization awareness, disciplined DTO design, and defensive API development in microservice ecosystems.
메타데이터
- post_id
- c77a69d83302
- slug
- tales-from-the-terminal-season-1-episode-1-c77a69d83302
- url
- https://medium.com/@akawadia25/tales-from-the-terminal-season-1-episode-1-c77a69d83302
- canonical_url
- https://medium.com/@akawadia25/tales-from-the-terminal-season-1-episode-1-c77a69d83302
- author_url
- https://medium.com/@akawadia25
- status
- ok
- fetched_at
- 2026-08-05 12:37:12