Introduction

By default, Jackson ignores unknown JSON properties during deserialization. While this provides forward compatibility, it also silently drops data when there are typos in field names or when the API response schema changes. Enabling strict mode makes Jackson throw UnrecognizedPropertyException for any unknown property, catching schema mismatches early.

Symptoms

  • Jackson silently ignores extra fields - data loss without errors
  • com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "usernmae" when strict mode enabled
  • API contract violations not detected in production
  • Zero values in Java objects when JSON field names don't match
  • Tests pass with incorrect mock data

```java // JSON from API: {"id": 1, "usernmae": "John", "email": "john@example.com"} // Typo: "usernmae" instead of "name"

public class User { public Long id; public String name; public String email; }

// Default behavior: silently ignores "usernmae", user.name = null // With FAIL_ON_UNKNOWN_PROPERTIES: throws UnrecognizedPropertyException ```

Common Causes

  • API field name typos in upstream service
  • API version changes adding or renaming fields
  • Java class field names not matching JSON (case sensitivity)
  • Missing @JsonProperty annotations for snake_case fields
  • Mock test data not matching production API schema

Step-by-Step Fix

  1. 1.Enable strict mode globally:
  2. 2.```java
  3. 3.ObjectMapper mapper = new ObjectMapper();
  4. 4.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);

// Or with builder (Jackson 2.x) ObjectMapper mapper = JsonMapper.builder() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) // false = ignore .build();

// For strict parsing: ObjectMapper strictMapper = JsonMapper.builder() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); ```

  1. 1.Use @JsonIgnoreProperties at class level:
  2. 2.```java
  3. 3.// Ignore specific unknown fields (selective tolerance)
  4. 4.@JsonIgnoreProperties(ignoreUnknown = true)
  5. 5.public class User {
  6. 6.public Long id;
  7. 7.public String name;
  8. 8.}

// Or fail on specific fields @JsonIgnoreProperties(ignoreUnknown = false) // explicit strict mode public class User { public Long id; public String name; } ```

  1. 1.Handle API versioning with multiple DTOs:
  2. 2.```java
  3. 3.// API v1 response
  4. 4.public class UserV1 {
  5. 5.public Long id;
  6. 6.public String name;
  7. 7.}

// API v2 response (added fields) public class UserV2 { public Long id; public String name; public String email; public String phone; }

// Versioned deserializer public class UserDeserializer extends StdDeserializer<User> { private final ObjectMapper v1Mapper; private final ObjectMapper v2Mapper;

public UserDeserializer() { super(User.class); v1Mapper = new ObjectMapper(); v2Mapper = new ObjectMapper(); v2Mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); }

@Override public User deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.getCodec().readTree(p); if (node.has("phone")) { return v2Mapper.convertValue(node, UserV2.class); } return v1Mapper.convertValue(node, UserV1.class); } } ```

  1. 1.Validate in Spring Boot REST controllers:
  2. 2.```java
  3. 3.@RestController
  4. 4.public class UserController {

private static final ObjectMapper STRICT_MAPPER = JsonMapper.builder() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build();

@PostMapping("/users") public ResponseEntity<User> createUser(@RequestBody String rawJson) { try { User user = STRICT_MAPPER.readValue(rawJson, User.class); return ResponseEntity.ok(userService.save(user)); } catch (UnrecognizedPropertyException e) { return ResponseEntity.badRequest() .body("Unknown field: " + e.getPropertyName()); } catch (JsonProcessingException e) { return ResponseEntity.badRequest() .body("Invalid JSON: " + e.getMessage()); } } } ```

Prevention

  • Enable FAIL_ON_UNKNOWN_PROPERTIES in production, disable in tests with mock data
  • Use JSON Schema validation alongside Jackson deserialization
  • Add contract tests that validate production API responses against DTOs
  • Use @JsonAlias for accepting multiple field name variants during migration
  • Log unknown properties at WARN level even when ignoring them:
  • ```java
  • mapper.addHandler(new DeserializationProblemHandler() {
  • @Override
  • public boolean handleUnknownProperty(DeserializationContext ctxt,
  • JsonParser p, JsonDeserializer<?> deserializer,
  • Object beanOrClass, String propertyName) {
  • log.warn("Unknown property '{}' in {}", propertyName, beanOrClass.getClass());
  • return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
  • }
  • });
  • `