To ignore null fields with Jackson, we annotate the class or the field with @JsonInclude(JsonInclude.Include.NON_NULL), and Jackson leaves every property with a null value out of the JSON. The value NON_ABSENT also skips an empty Optional, and NON_EMPTY also skips empty strings and empty collections.
We ignore these values to keep JSON responses small and clean, for example when a REST API returns an object with many optional fields and the client must not see “notes”:null for every missing value.
The following example serializes one Recipe record with each Include value on the class, with the JSON output for each value as a comment.
@JsonInclude(JsonInclude.Include.NON_NULL) // or NON_ABSENT, NON_EMPTY, NON_DEFAULT
record Recipe(String name, String notes, List<String> tags, Optional<Integer> rating, int servings) {}
Recipe recipe = new Recipe("Pancakes", null, List.of(), Optional.empty(), 0);
String json = new ObjectMapper().writeValueAsString(recipe);
// no annotation: {"name":"Pancakes","notes":null,"tags":[],"rating":null,"servings":0}
// NON_NULL: {"name":"Pancakes","tags":[],"rating":null,"servings":0}
// NON_ABSENT: {"name":"Pancakes","tags":[],"servings":0}
// NON_EMPTY: {"name":"Pancakes","servings":0}
// NON_DEFAULT: {"name":"Pancakes"}
Notice that NON_NULL still writes “rating”:null for the empty Optional, and only NON_DEFAULT removes the 0 of the primitive servings field.
Next, we look at what Jackson counts as null, absent or empty, and apply @JsonInclude per class, per field or to the whole JsonMapper. After that, we cover the Spring Boot property and null entries of a Map, and we write our own rule for what counts as empty.
1. Setup
The examples use Jackson 3.2.3 on Java 25. In Jackson 3, the databind module has the group ID tools.jackson.core and the package tools.jackson.databind, but the annotations such as @JsonInclude stay in the package com.fasterxml.jackson.annotation. The jackson-databind dependency brings in jackson-core and jackson-annotations.
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.3</version>
</dependency>
Projects still on Jackson 2 use the group ID com.fasterxml.jackson.core and version 2.22.3. The @JsonInclude annotation works the same way in both versions, and section 4 shows how the global setting changed.
For demo purposes, we use the following Recipe record. It has one property of each kind that Jackson treats differently, namely a String, a List, an Optional and a primitive int.
record Recipe(String name, String notes, List<String> tags,
Optional<Integer> rating, int servings) {}
By default, Jackson writes every property, including the null ones.
ObjectMapper mapper = new ObjectMapper();
Recipe recipe = new Recipe("Pancakes", null, List.of(), Optional.empty(), 0);
String json = mapper.writeValueAsString(recipe); // {"name":"Pancakes","notes":null,"tags":[],"rating":null,"servings":0}
2. Difference between NULL, Empty and Absent Values
Before writing any annotation, we need to know what each kind of value means to Jackson, so that we can choose the right option. Jackson’s enum JsonInclude.Include has one value for each definition, and each definition includes the one before it.
2.1. NULL Values
Null values are all fields with a null reference, such as notes in our Recipe. The value Include.NON_NULL skips them.
2.2. Absent Values
Absent values consist of two types.
- Values that are null.
- The “absent” value of referential types, namely an empty Optional or an AtomicReference that holds null.
Jackson writes an empty Optional as null, so Include.NON_NULL does not remove it. The value Include.NON_ABSENT skips both kinds. Jackson 3 supports Optional without extra setup, whereas Jackson 2 needs the jackson-datatype-jdk8 module and otherwise throws an InvalidDefinitionException saying that the Java 8 optional type is not supported by default.
2.3. Empty Values
Empty values include null and absent values, plus a few more types.
- Values that are null.
- The “absent” value of referential types such as Optional or AtomicReference.
- Empty strings of length 0.
- Empty containers such as arrays, collections and maps of size 0.
The value Include.NON_EMPTY skips all of them. A primitive 0 or false is not empty, so NON_EMPTY still writes “servings”:0. Only Include.NON_DEFAULT also skips the default values of primitives and their wrappers.
In the Recipe from the intro, each field falls into one row of the table, so the table predicts the JSON for each Include value. The Optional is the one case where NON_NULL still writes the field, as null.
| Value in the field | NON_NULL | NON_ABSENT | NON_EMPTY | NON_DEFAULT |
|---|---|---|---|---|
| null | skipped | skipped | skipped | skipped |
| Optional.empty() | written as null | skipped | skipped | skipped |
| “” | written | written | skipped | skipped |
| List.of() | written | written | skipped | skipped |
| 0 (int) | written | written | written | skipped |
The diagram shows how the four sets fit inside each other. Each value removes what the value before it removes, plus one more kind of value.

3. Ignoring NULL, Empty and Absent Values
To ignore null and empty values, we use one of the values present in the JsonInclude.Include enum.
- JsonInclude.Include.NON_NULL
- JsonInclude.Include.NON_ABSENT
- JsonInclude.Include.NON_EMPTY
- JsonInclude.Include.NON_DEFAULT
We put the @JsonInclude annotation on a class, on a field or on a record component. The annotation affects only serialization (Java to JSON), and reading JSON works the same as before.
3.1. At Class Level
When applying @JsonInclude at class level, all the fields with null values will be ignored during serialization.
@JsonInclude(JsonInclude.Include.NON_NULL)
record Recipe(String name, String notes, List<String> tags,
Optional<Integer> rating, int servings) {}
Let’s test this out with the same Recipe instance as in section 1.
Recipe recipe = new Recipe("Pancakes", null, List.of(), Optional.empty(), 0);
String json = mapper.writeValueAsString(recipe); // {"name":"Pancakes","tags":[],"rating":null,"servings":0}
We can see that notes is gone, but rating is still in the JSON as null, because an empty Optional is absent, not null.
3.2. At Fields Level
When applying @JsonInclude at the field level, only the annotated fields with null values will be ignored. In a record, we put the annotation on the record component.
record Recipe(String name,
@JsonInclude(JsonInclude.Include.NON_NULL) String notes,
List<String> tags) {}
The generated JSON skips the notes field when it is null. The tags field is still written as null because it has not been annotated.
Recipe recipe = new Recipe("Pancakes", null, null);
String json = mapper.writeValueAsString(recipe); // {"name":"Pancakes","tags":null}
3.3. Skipping Absent, Empty and Default Values
The other Include values work the same way at class or field level. For example, a recipe API sends a list of recipes to a mobile app, and most recipes have no tags and no rating yet. With NON_EMPTY, the app gets neither “tags”:[] nor “rating”:null for those recipes.
@JsonInclude(JsonInclude.Include.NON_ABSENT)
record RecipeNonAbsent(String name, String notes, List<String> tags,
Optional<Integer> rating, int servings) {}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
record RecipeNonEmpty(String name, String notes, List<String> tags,
Optional<Integer> rating, int servings) {}
record RecipeNonDefault(String name,
@JsonInclude(JsonInclude.Include.NON_DEFAULT) int servings,
@JsonInclude(JsonInclude.Include.NON_DEFAULT) String notes) {}
String absent = mapper.writeValueAsString(
new RecipeNonAbsent("Pancakes", null, List.of(), Optional.empty(), 0)); // {"name":"Pancakes","tags":[],"servings":0}
String empty = mapper.writeValueAsString(
new RecipeNonEmpty("Pancakes", "", List.of(), Optional.empty(), 0)); // {"name":"Pancakes","servings":0}
String noDefaults = mapper.writeValueAsString(
new RecipeNonDefault("Pancakes", 0, "")); // {"name":"Pancakes"}
String withValues = mapper.writeValueAsString(
new RecipeNonDefault("Pancakes", 4, "Rest the batter")); // {"name":"Pancakes","servings":4,"notes":"Rest the batter"}
Be careful with NON_DEFAULT on numbers and booleans. A client cannot tell a skipped “servings”:0 from a missing value, so we use NON_DEFAULT only for fields where 0 or false carries no meaning.
On a class with a no-argument constructor, NON_DEFAULT works differently. Jackson creates one instance with that constructor and skips every property whose value equals the value in that instance. For example, when the constructor sets servings to 2, Jackson skips 2 and writes 0. A record has no such constructor, so Jackson falls back to the field rule, i.e. empty values plus 0 and false.
4. Ignoring Null Fields Globally in Jackson
If ignoring null fields is the application’s default behavior, then it makes sense to configure it globally instead of annotating every class. In Jackson 3, a JsonMapper is immutable, so we set the default inclusion on its builder.
JsonMapper mapper = JsonMapper.builder()
.changeDefaultPropertyInclusion(incl -> incl
.withValueInclusion(JsonInclude.Include.NON_NULL)
.withContentInclusion(JsonInclude.Include.NON_NULL))
.build();
Recipe recipe = new Recipe("Pancakes", null, null, Optional.empty(), 0);
String json = mapper.writeValueAsString(recipe); // {"name":"Pancakes","rating":null,"servings":0}
Now this mapper will ignore all the null fields for all the classes it serializes. The value inclusion applies to the properties of our classes. The content inclusion applies to the entries of a Map, as we will see in section 6.
An annotation on a class or a field wins over the global setting. For example, @JsonInclude(JsonInclude.Include.ALWAYS) on notes writes “notes”:null even with the global NON_NULL mapper.
record RecipeAlways(String name, @JsonInclude(JsonInclude.Include.ALWAYS) String notes) {}
String json = mapper.writeValueAsString(new RecipeAlways("Pancakes", null)); // {"name":"Pancakes","notes":null}
In Jackson 2, the ObjectMapper is mutable, and we call setDefaultPropertyInclusion() on it. The older method setSerializationInclusion() still works, but it is deprecated since Jackson 2.21 and does not exist in Jackson 3.
ObjectMapper mapper = new ObjectMapper();
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
5. Ignoring Null Values in Spring Boot
A Spring Boot application creates the JsonMapper for us, so we do not build one ourselves. Spring Boot 4.1.1 uses Jackson 3, and the property spring.jackson.default-property-inclusion sets the global inclusion for the mapper that Spring MVC uses for request and response bodies.
spring.jackson.default-property-inclusion=non_null
The property accepts every JsonInclude.Include value in lower case, such as non_empty. Spring Boot sets the property as both the value and the content inclusion, so it also removes the null entries of a Map.
When we need more control in Java code, we declare a JsonMapperBuilderCustomizer bean. Spring Boot passes the builder to it before it creates the mapper.
@Bean
JsonMapperBuilderCustomizer skipNulls() {
return builder -> builder.changeDefaultPropertyInclusion(
incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL));
}
Applications on Spring Boot 3 use Jackson 2, where the same property works and the customizer type is Jackson2ObjectMapperBuilderCustomizer. For more on the response side, read about Spring Boot REST APIs and the REST resource design rules.
6. Ignoring Null Entries in a Map
The value inclusion on a Map field checks the map itself, not the entries inside it. For example, an ingredient map can hold “salt” -> null when the amount is “to taste”, and NON_NULL on the field keeps that entry, because the map itself is not null.
To skip the entries with null values, we set the content attribute of @JsonInclude.
record Ingredients(@JsonInclude(content = JsonInclude.Include.NON_NULL) Map<String, String> amounts) {}
Map<String, String> amounts = new LinkedHashMap<>();
amounts.put("flour", "200 g");
amounts.put("salt", null);
String json = mapper.writeValueAsString(new Ingredients(amounts)); // {"amounts":{"flour":"200 g"}}
We can use both attributes together, e.g. @JsonInclude(value = NON_EMPTY, content = NON_NULL) skips an empty map and the null entries of a non-empty map.
7. Using Custom Filter
We can further customize the check for emptiness by creating a custom filter class and overriding its equals() method. Jackson creates one instance of the filter class and calls equals() with the property value. If equals() returns true, the value is excluded (that is, filtered out), and if it returns false, the value is included. Jackson calls equals() for null values too, so the filter must handle null.
For example, a recipe form sends notes as ” “ when the user types only spaces. The following filter skips null and blank strings.
class BlankStringFilter {
@Override
public boolean equals(Object value) {
return value == null || (value instanceof String text && text.isBlank());
}
@Override
public int hashCode() {
return 0;
}
}
And register it as a custom filter for a field as follows.
record Recipe(String name,
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = BlankStringFilter.class)
String notes) {}
String blank = mapper.writeValueAsString(new Recipe("Pancakes", " ")); // {"name":"Pancakes"}
String filled = mapper.writeValueAsString(new Recipe("Pancakes", "Rest the batter")); // {"name":"Pancakes","notes":"Rest the batter"}
8. Using Custom Serializer
Another way to filter out empty values is by overriding the isEmpty() method of a custom serializer class. If isEmpty() returns true, the field is excluded from serialization when it has @JsonInclude(JsonInclude.Include.NON_EMPTY).
The custom serializer fits when the type has its own idea of “empty”. For example, a CookingTime of 0 minutes means that nobody entered a time, so we do not want “0 min” in the JSON. In Jackson 3, StdSerializer is in the package tools.jackson.databind.ser.std, and the methods take a SerializationContext.
record CookingTime(int minutes) {}
class CookingTimeSerializer extends StdSerializer<CookingTime> {
CookingTimeSerializer() {
super(CookingTime.class);
}
@Override
public void serialize(CookingTime value, JsonGenerator gen, SerializationContext ctxt) {
gen.writeString(value.minutes() + " min");
}
@Override
public boolean isEmpty(SerializationContext ctxt, CookingTime value) {
return value == null || value.minutes() == 0;
}
}
record Recipe(String name,
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonSerialize(using = CookingTimeSerializer.class)
CookingTime cookingTime) {}
String timed = mapper.writeValueAsString(new Recipe("Pancakes", new CookingTime(15))); // {"name":"Pancakes","cookingTime":"15 min"}
String untimed = mapper.writeValueAsString(new Recipe("Pancakes", new CookingTime(0))); // {"name":"Pancakes"}
In Jackson 2, the same method has the signature isEmpty(SerializerProvider provider, T value).
9. Jackson Null Values FAQs
9.1. What Is the Difference Between NON_NULL and NON_EMPTY?
The value NON_NULL skips only null values. The value NON_EMPTY also skips an empty Optional, an empty string, and an empty collection, array or map, as the table in section 2 shows. Neither of them skips a primitive 0 or false.
9.2. Does @JsonInclude Affect Deserialization?
No. The annotation controls only which properties Jackson writes. When Jackson reads JSON without a property, it leaves the field null, sets an Optional to Optional.empty() and a primitive to its default value.
Recipe recipe = mapper.readValue("{\"name\":\"Pancakes\"}", Recipe.class);
// Recipe[name=Pancakes, notes=null, tags=null, rating=Optional.empty, servings=0]
9.3. What Is the Difference Between @JsonIgnore and @JsonInclude?
The annotation @JsonIgnore removes a property always and in both directions, whatever its value. The annotation @JsonInclude removes a property only when its value matches the rule, and only when writing JSON.
record Recipe(String name, @JsonIgnore String notes) {}
String json = mapper.writeValueAsString(new Recipe("Pancakes", "secret")); // {"name":"Pancakes"}
Recipe read = mapper.readValue("{\"name\":\"Pancakes\",\"notes\":\"secret\"}", Recipe.class); // notes = null
9.4. Why Is setSerializationInclusion() Deprecated?
Jackson 2.21 deprecated setSerializationInclusion() in favor of setDefaultPropertyInclusion(), which also accepts a value and a content inclusion. Jackson 3 removed both setters from the mapper, because a Jackson 3 mapper is immutable, so we use changeDefaultPropertyInclusion() on the builder, as shown in section 4.
10. Conclusion
In this short tutorial, we learned to ignore the null, empty and absent values when serializing a Java object to JSON. The @JsonInclude annotation sets the rule for a class or a field, and NON_NULL, NON_ABSENT, NON_EMPTY and NON_DEFAULT remove a wider set of values each.
For an application-wide rule, we set the default inclusion on the JsonMapper builder in Jackson 3, or the spring.jackson.default-property-inclusion property in Spring Boot. Entries of a Map need the content inclusion, and a custom filter or the isEmpty() method of a custom serializer handles our own definition of empty.
11. References
Happy Learning !!