Customising Property Names and Inclusion

Rename JSON properties, switch naming conventions, hide fields and drop nulls with Jackson annotations or global spring.jackson settings.

Syntax@JsonProperty("customer_id") @JsonAlias("customerId") long customerId @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)

Java names and JSON names do not always match. A partner may want snake_case, a legacy client may still send an old field name, and some values should never leave the server. Jackson handles all of this with annotations on the DTO or with a few global properties.

Renaming

@JsonProperty("customer_id") renames one property in both directions. Renaming is a breaking change: after it, the old name is no longer read, so a client still sending customerId has its value ignored. Add @JsonAlias("customerId") to keep accepting the old name on input while you write only the new one.

To change the convention for a whole type, use @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) from tools.jackson.databind.annotation. To change it for the whole application, set spring.jackson.property-naming-strategy=SNAKE_CASE. The global switch affects every response, including ones you forgot about, so decide it on day one or not at all.

Leaving properties out

@JsonInclude controls when a property is written:

  • NON_NULL drops null values.
  • NON_ABSENT also drops an empty Optional.
  • NON_EMPTY also drops empty strings, collections and maps.

spring.jackson.default-property-inclusion=non_null applies a rule globally. Smaller payloads are nice, but think about the client: with null dropped it can no longer tell "this order has no discount" from "this server is too old to know about discounts". NON_EMPTY on a collection is usually a mistake for the same reason; an empty items array is information.

Hiding and one-way properties

@JsonIgnore removes a property in both directions. @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) reads a value from requests but never writes it in responses, which suits a password on a sign-up DTO. @JsonIgnoreProperties({"internalNotes"}) on the type does the same as @JsonIgnore for named properties. Annotations are a safety net, not a design: a field that must never be returned is best left off the response record entirely.

@JsonPropertyOrder fixes the order of properties, which helps when humans read the JSON or when a client wrongly depends on order. Order has no meaning in JSON objects, so never build logic on it.

Example

Example · java
import com.fasterxml.jackson.annotation.JsonInclude;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import tools.jackson.databind.PropertyNamingStrategies;
import tools.jackson.databind.annotation.JsonNaming;

// A partner API that expects snake_case and no null values.
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record PartnerInvoice(
        String invoiceId,
        String customerName,
        BigDecimal totalAmount,
        BigDecimal discountAmount,   // omitted when null
        Instant paidAt,              // omitted until the invoice is paid
        List<String> lineDescriptions) {
}

// {"invoice_id":"INV-7","customer_name":"Ada Ltd","total_amount":120.00,
//  "line_descriptions":["Consulting"]}

When to use it

  • Integrate with a partner API that requires snake_case property names while the rest of your API stays camelCase.
  • Rename a confusing field in a public API and keep accepting the old name from mobile apps that have not updated.
  • Accept a password on sign-up and guarantee it never appears in a response body or a JSON log line.

More examples

Global naming and inclusion

One line each changes every request and response, including error bodies and any object you serialise with the injected mapper. That is convenient for a new service and dangerous for a live one, where every existing client sees the change at once.

Example · properties
# Every DTO in the application: snake_case names, nulls left out.
spring.jackson.property-naming-strategy=SNAKE_CASE
spring.jackson.default-property-inclusion=non_null

Renaming a field without breaking clients

Responses now use <code>displayName</code>, while requests from older app versions that still send <code>name</code> or <code>fullName</code> keep working. Log or count use of the old names if you can, then remove the alias once no client sends them.

Example · java
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;

public record UpdateProfileRequest(
        @JsonProperty("displayName") @JsonAlias({"name", "fullName"}) String displayName,
        @JsonProperty("email") @JsonAlias("mail") String email) {
}

Write-only and ignored properties

If this record is ever logged with the injected mapper or echoed in a response, the password is left out. <code>@JsonIgnore</code> on <code>admin</code> blocks mass assignment: a client sending <code>"admin": true</code> is simply ignored.

Example · java
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

public record SignupRequest(
        String email,
        // Read from the request body, never written back out.
        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String password,
        // Not bound from JSON at all; a client cannot set it.
        @JsonIgnore Boolean admin) {

    public SignupRequest {
        admin = Boolean.FALSE;
    }
}

Discussion

  • Be the first to comment on this lesson.