Home > Blockchain >  Spring Data Rest - POST request return status 400
Spring Data Rest - POST request return status 400

Time:08-11

After updating Spring Boot from version 1.5 to 2.7, POST requests returns status 400.

I have Feed repository:

interface FeedRepository : PagingAndSortingRepository<Feed, Long> { 
// Custom methods 
}

Spring Data Rest generate default controller for him, so I can make requests on: localhost:8080/api/feeds

Feed class:

@Entity
@DiscriminatorValue("feed")
class Feed(
    id: Long, //Superclass Fields,

    @ManyToOne
    val parent: Feed?,

    @OneToMany(mappedBy = "parent", cascade = [CascadeType.REMOVE])
    val children: List<Feed>,

    @Column(name = "extension_id")
    var extensionId: String?,

    @Column(name = "create_date")
    var created: Date = Calendar.getInstance().time

) : Resource(id, //Superclass Fields)

Resource class:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes(value = [
// SubTypes
JsonSubTypes.Type(value = Feed::class, name = "feed"),
])
abstract class Resource(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    override val id: Long,

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "resource_tags",
            joinColumns = [JoinColumn(name = "resource_id")],
            inverseJoinColumns = [JoinColumn(name = "tag_id")]
    )
    val tags: Set<Tag> = Collections.emptySet(),

    @JsonIgnore
    @OneToMany(mappedBy = "resource", cascade = [CascadeType.REMOVE])
    val menuItems: List<Menu> = ArrayList(0),

    // Another Fields
)

ObjectMapper Bean:

@Bean(name = arrayOf("OBJECT_MAPPER_BEAN"))
fun jsonObjectMapper(): ObjectMapper {
    return Jackson2ObjectMapperBuilder.json()
            .serializationInclusion(JsonInclude.Include.ALWAYS)
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .featuresToDisable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
            .featuresToDisable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
            .modules(JavaTimeModule())
            .build<ObjectMapper>()
}

If I do a save request (POST localhost:8080/api/feeds) with payload:

{
  "type": "packages.Feed",
  "parent": "https://localhost:8080/api/feeds/251",
  "extensionId": null,
  "title": {
    "localizedStrings": [
        "https://localhost:8080/api/localizedStrings/4319"
    ]
  }
}

I get status 400 and error:

JSON parse error: Instantiation of [simple type, class Feed] value failed for JSON property tags due to missing (therefore NULL) value for creator parameter tags which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class Feed] value failed for JSON property tags due to missing (therefore NULL) value for creator parameter tags which is a non-nullable type\n at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 10, column: 1] (through reference chain: Feed["tags"])

It looks like the Jackson library handles the request body a little differently, since this worked before the update.

I tried to specify an annotation for the field "tags" in Feed class @JsonProperty(required = false), but it did not help.

Any ideas how to fix this? I would be very grateful for help.

CodePudding user response:

Some essential code is missing in the question, e.g. how the property tags, which is mentioned in the error message, gets its value. Most likely does class Feed continue like this

class Feed(
    id: Long,
    tags: Set<Tag>,

    [other code here]

) : Resource(id, tags, //Superclass Fields)

If this is the case, Jackson will have issues with constructing class Feed because default value for tags is missing (even though it's defined in parent). In that case, adding a default value to tags in class Feed should fix the issue.

class Feed(
    id: Long,
    tags: Set<Tag> = Collections.emptySet(),

    [other code here]

CodePudding user response:

I fixed the issue by adding the configuration to the Jackson Kotlin module.

Now a ObjectMapper creating looks like:

@Bean(name = arrayOf("OBJECT_MAPPER_BEAN"))
fun jsonObjectMapper(): ObjectMapper {
            return Jackson2ObjectMapperBuilder.json()
            .serializationInclusion(JsonInclude.Include.ALWAYS)
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .featuresToDisable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
            .featuresToDisable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
            .modules(JavaTimeModule(), KotlinModule.Builder()
                .configure(KotlinFeature.NullToEmptyCollection, true)
                .configure(KotlinFeature.NullToEmptyMap, true)
                .configure(KotlinFeature.NullIsSameAsDefault, true)
                .configure(KotlinFeature.SingletonSupport, false)
                .configure(KotlinFeature.StrictNullChecks, false)
                .build()
            )
            .build<ObjectMapper>()
}

After running a couple of tests, I can say that everything works as expected.

  • Related