← Back to list

Tales from the Terminal — Season 1 — Episode 1

Episode 1 — The Case of the Vanishing Timezone: Director’s Cut

Aman Kawadia · 2025-08-27 15:29 · 2 claps · 2.9 min read
#serialization #api-design #handling-null #microservices #java-backend
Open on Medium ↗
Wiki topics: 🌐 · Web Development 🎬 · Film & Television

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 boolean with a default. It’s safer.
  • From a caller’s perspective, sending null when unsure is reasonable; the server should guard or set defaults.
  • Serialization matters: an absent field isn’t the same as a null field. 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": null by default.
  • On the server: — When a field is absent, the default initializer (= true) remains untouched. – When null is 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 null entirely:@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 Boolean but preserve defaults when null is 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": null is 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 null is 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