Spring Boot @RequestParam as List type

Jan 16, 2024
 — by 
Kris Kratz
 in 

Here’s one of those funny little problems that aren’t quite obvious.

I use Spring Boot as the API for a NextJS app to access the data store. Naturally, I’m sending all sorts of variables, and in this case I am sending an Array (or List) of Longs. And I want to keep the code compact, meaning I don’t want to have to deal with JSON myself using Jackson’s object mapper if I can avoid it (Note: I show how to get object mapper working at the end).

My controller in Spring Boot has a PostMapping function that looks like this.

@PostMapping("/my-endpoint")
@ResponseBody
fun postArticleUpdateCategoryList(
    @RequestParam categoryIdList: List,
) : Map {

    val success = articleService.updateArticleCategoryList(categoryIdList)
    return mapOf("success" to success)
}

Here’s the gotcha. Naturally, I want to send the data as JSON because we do that very often in JavaScript. However, that throws an error. Spring complains that it can’t convert a String to Array!

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.List'; For input string: "[15,16]"]

This doesn’t work:

const formData = new FormData()
formData.set("categoryIdList", JSON.stringify(categoryIdList)) // The problem is here.

fetch(apiUrl + "/my-endpoint", {
    method: 'POST',
    body: formData
})

I guess Spring can’t convert JSON (“[15,16]”) to an Array in the controller like that.

Let’s change the controller to Array and see what happens when I put a break point in to pause the action.

Aha! It looks like Spring just takes the comma separated values and generates a list. In this case, “[15” and “16]” will cause an exception when attempting to cast to Long.

The way to make this work is to join the list with commas in JavaScript, instead of stringifying it.

Here’s my fetch function that actually works:

const formData = new FormData()
formData.set("categoryIdList", categoryIdList.join(',')) // This works

fetch(apiUrl + "/my-endpoint", {
    method: 'POST',
    body: formData
})

If you absolutely must accept JSON into the controller here’s how I got that working:

val mapper = jacksonObjectMapper()

@PostMapping("/my-endpoint")
@ResponseBody
fun postArticleUpdateCategoryList(
    @RequestParam categoryIdListJson: String,
    auth: Authentication
) : Map {

    val categoryIdList = try {
        mapper.readValue(categoryIdListJson, List::class.java)
    } catch (e: JsonProcessingException) {
        return mapOf("success" to false)
    } catch (e: JsonMappingException) {
        return mapOf("success" to false)
    }

    val success = articleService.updateArticleCategoryList(auth.name, categoryIdList)
    return mapOf("success" to success)
}