Search code examples
htmlspringspring-bootspring-mvcthymeleaf

Binding a List in Thymeleaf


I have a Model object with this property:

private List<MultipartFile> files;

but when I bind them in the template:

<input type="file" id="file1" class="form-control" th:field="*{files[0]}" name="files" accept=".gif, .jpg, .png, .jpeg" />
<input type="file" id="file2" class="form-control" th:field="*{files[1]}" name="files" accept=".gif, .jpg, .png, .jpeg" />

I have this error:

    ... 85 common frames omitted
Caused by: org.springframework.beans.NullValueInNestedPathException: Invalid property 'files' of bean class [com.domain.frontend.UserPayload]: Could not instantiate property type [org.springframework.web.multipart.MultipartFile] to auto-grow nested property path; nested exception is java.lang.NoSuchMethodException: org.springframework.web.multipart.MultipartFile.<init>()
    at org.springframework.beans.AbstractNestablePropertyAccessor.newValue(AbstractNestablePropertyAccessor.java:919)
    at org.springframework.beans.AbstractNestablePropertyAccessor.growCollectionIfNecessary(AbstractNestablePropertyAccessor.java:785)
    at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:654)
    ... 96 common frames omitted
Caused by: java.lang.NoSuchMethodException: org.springframework.web.multipart.MultipartFile.<init>()

Solution

  • I had a similar problem not too long ago. I solved it by wrapping the List<> object in a Data Transfer Object then modifying the List<> as a field instead of the object itself. Here is your solution: DTO class

    public class FileDTO {
    
        private List<File> files;
    
        public FileDTO() {
            files = new ArrayList<>();
        }
    
        public List<File> getFiles() {
            return files;
        }
    
        public void setFiles(List<File> files) {
            this.files = files;
        }
    }
    

    thymeleaf view:

    <form action="/example" th:object="${dto}">
        <input type="file" th:each="file, itemStat : *{files}" th:id="${'file' + ${itemStat.index}}"
               th:field="*{files[__${itemStat.index}__]}" name="files" accept=".gif, .jpg, .png, .jpeg">
        <input type="submit" value="Submit">
    </form>
    

    Sorry if that was a little much to sort through. But the idea is that the List<> has to be a field not the form backing object itself then to iterate over the List<> field you can use the loop shown.