title: Converting & validating CSV file upload in Spring MVC
body: <p>I have a Customer entity that contains a list of Sites, as follows:</p>
  
  <pre><code>public class Customer {
  
      @Id
      @GeneratedValue
      private int id;
  
      @NotNull
      private String name;
  
      @NotNull
      @AccountNumber
      private String accountNumber;
  
      @Valid
      @OneToMany(mappedBy="customer")
      private List&lt;Site&gt; sites
  }
  
  public class Site {
  
      @Id
      @GeneratedValue
      private int id;
  
      @NotNull
      private String addressLine1;
  
      private String addressLine2;
  
      @NotNull
      private String town;
  
      @PostCode
      private String postCode;
  
      @ManyToOne
      @JoinColumn(name="customer_id")
      private Customer customer;
  }
  </code></pre>
  
  <p>I am in the process of creating a form to allow users to create a new Customer by entering the name &amp; account number and supplying a CSV file of sites (in the format "addressLine1", "addressLine2", "town", "postCode"). The user's input needs to be validated and errors returned to them (e.g. "file is not CSV file", "problem on line 7").</p>
  
  <p>I started off by creating a Converter to receive a MultipartFile and convert it into a list of Site:</p>
  
  <pre><code>public class CSVToSiteConverter implements Converter&lt;MultipartFile, List&lt;Site&gt;&gt; {
  
      public List&lt;Site&gt; convert(MultipartFile csvFile) {
  
          List&lt;Site&gt; results = new List&lt;Site&gt;();
  
          /* open MultipartFile and loop through line-by-line, adding into List&lt;Site&gt; */
  
          return results;
      }
  }
  </code></pre>
  
  <p>This worked but there is no validation (i.e. if the user uploads a binary file or one of the CSV rows doesn't contain a town), there doesn't seem to be a way to pass the error back (and the converter doesn't seem to be the right place to perform validation).</p>
  
  <p>I then created a form-backing object to receive the MultipartFile and Customer, and put validation on the MultipartFile:</p>
  
  <pre><code>public class CustomerForm {
  
      @Valid
      private Customer customer;
  
      @SiteCSVFile
      private MultipartFile csvFile;
  }
  
  @Documented
  @Constraint(validatedBy = SiteCSVFileValidator.class)
  @Target(ElementType.FIELD)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface SiteCSVFile {
  
      String message() default "{SiteCSVFile}";
  
      Class&lt;?&gt;[] groups() default {};
  
      Class&lt;? extends Payload&gt;[] payload() default {};
  }
  
  public class SiteCSVFileValidator implements ConstraintValidator&lt;SiteCSVFile, MultipartFile&gt; {
  
      @Override
      public void initialize(SiteCSVFile siteCSVFile) { }
  
      @Override
      public boolean isValid(MultipartFile csvFile, ConstraintValidatorContext cxt) {
  
          boolean wasValid = true;
  
          /* test csvFile for mimetype, open and loop through line-by-line, validating number of columns etc. */
  
          return wasValid;
      }
  }
  </code></pre>
  
  <p>This also worked but then I have to re-open the CSV file and loop through it to actually populate the List within Customer, which doesn't seem that elegant:</p>
  
  <pre><code>@RequestMapping(value="/new", method = RequestMethod.POST)
  public String newCustomer(@Valid @ModelAttribute("customerForm") CustomerForm customerForm, BindingResult bindingResult) {
  
      if (bindingResult.hasErrors()) {
          return "NewCustomer";
      } else {
  
          /* 
             validation has passed, so now we must:
             1) open customerForm.csvFile 
             2) loop through it to populate customerForm.customer.sites 
          */
  
          customerService.insert(customerForm.customer);
  
          return "CustomerList";
      }
  }
  </code></pre>
  
  <p>My MVC config limits file uploads to 1MB:</p>
  
  <pre><code>@Bean
  public MultipartResolver multipartResolver() {
      CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
      multipartResolver.setMaxUploadSize(1000000);
      return multipartResolver;
  }
  </code></pre>
  
  <p>Is there a spring-way of converting AND validating at the same time, without having to open the CSV file and loop through it twice, once to validate and another to actually read/populate the data?</p>