Implementing a CSV Upload Parser with OpenCSV and Spring Boot
CSV uploads remain a practical way to import customer records, product catalogues, invoices and transactions into a Spring Boot application. OpenCSV handles common parsing concerns while Spring Boot provides clean endpoints, validation and service-layer integration.
A reliable importer must do more than split text on commas. It should recognise quoted values, escaped characters, different line endings and optional headers. It also needs to reject malformed rows without allowing one bad record to obscure the useful data.
Australian applications often process dates such as 31/12/2025, postcodes such as 3000 for Melbourne or 2000 for Sydney, and prices in Australian dollars. A parser should preserve these values accurately rather than relying on assumptions designed for another market.
The example below accepts a multipart upload, maps each row to a Java bean, validates required fields and returns a useful response. In production, privacy, file-size limits and the Australian Privacy Act 1988 should be considered when uploaded data contains personal information.
Adding OpenCSV To A Spring Boot Project
Add OpenCSV to a Maven project with its current compatible version:
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>
Spring Boot already provides multipart support through Spring MVC. The following configuration limits the upload size, which helps prevent an accidentally large file from consuming application resources:
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=6MB
The limit should reflect the expected workload. A small retailer in Brisbane may upload a few hundred product rows, while a national distributor could require a scheduled batch process instead of a browser-based upload.
Mapping CSV Rows To Java Objects
Create a bean whose property names correspond to the CSV header. OpenCSV can map columns by name, making the format easier to understand and maintain:
public class CustomerRow {
private String name;
private String email;
private String postcode;
// getters and setters
}
The CSV file might contain:
name,email,postcode
Alex Nguyen,alex@example.com,3000
Taylor Smith,taylor@example.com,2000
A parser service can read the multipart file and convert it into objects:
@Service
public class CsvCustomerService {
public List<CustomerRow> parse(MultipartFile file) throws IOException {
try (Reader reader = new InputStreamReader(
file.getInputStream(), StandardCharsets.UTF_8)) {
HeaderColumnNameMappingStrategy<CustomerRow> strategy =
new HeaderColumnNameMappingStrategy<>();
strategy.setType(CustomerRow.class);
try (CsvToBean<CustomerRow> csvToBean =
new CsvToBeanBuilder<CustomerRow>(reader)
.withMappingStrategy(strategy)
.withIgnoreLeadingWhiteSpace(true)
.withIgnoreEmptyLine(true)
.build()) {
return csvToBean.parse();
}
}
}
}
UTF-8 is a sensible default for Australian systems because customer names and addresses may include accented characters or languages commonly used in Sydney, Melbourne and Perth. The service should also verify that the file is present and has a CSV-compatible content type before parsing.
Choosing A Safe Parsing Strategy
OpenCSV offers useful controls for different data sources. A header-based strategy is readable, while a column-position strategy is better when files have no header. The parser should define whether quotes, empty values and extra columns are accepted.
| Requirement | OpenCSV approach | Practical consideration |
|---|---|---|
| Header-based import | HeaderColumnNameMappingStrategy |
Clear when suppliers provide stable column names |
| Fixed column positions | ColumnPositionMappingStrategy |
Useful for legacy exports without headers |
| Ignore blank rows | withIgnoreEmptyLine(true) |
Prevents harmless trailing lines from becoming records |
| Trim surrounding spaces | withIgnoreLeadingWhiteSpace(true) |
Helps with manually edited files |
| Capture malformed rows | CsvException handling |
Return row-level feedback to the uploader |
| Preserve Australian dates | Custom converter | Parse formats such as dd/MM/yyyy explicitly |
For money values, use BigDecimal rather than double. For Australian dates, define a converter with DateTimeFormatter.ofPattern("dd/MM/yyyy") instead of relying on the server’s locale. This prevents a date such as 04/05/2025 from being interpreted inconsistently.
Validating Imported Data
Parsing confirms that a row has the expected structure; it does not prove that the data is valid. Add Bean Validation annotations to the mapped class:
public class CustomerRow {
@NotBlank
private String name;
@NotBlank
@Email
private String email;
@Pattern(regexp = "\\d{4}")
private String postcode;
// getters and setters
}
Validate each parsed object before saving it. A useful import response can report the row number, field name and reason for failure, such as “postcode must contain four digits”. This is more helpful than returning a generic HTTP 400 response.
For files containing contact details, avoid logging complete rows. The Australian Privacy Act 1988 and the Notifiable Data Breaches scheme make sensible data handling important, particularly when uploads contain names, email addresses or payment-related information.
Exposing The Upload Endpoint
A controller can connect the multipart request to the parsing service:
@RestController
@RequestMapping("/api/customers")
public class CustomerUploadController {
private final CsvCustomerService service;
public CustomerUploadController(CsvCustomerService service) {
this.service = service;
}
@PostMapping("/upload")
public ResponseEntity<List<CustomerRow>> upload(
@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(service.parse(file));
}
}
For a production endpoint, separate parsing from persistence. First parse and validate the complete file, then save it in a transaction. This prevents half an upload being stored when a later row is invalid.
A response containing imported count, rejected count and row-level errors works well for an administration screen. It also suits Australian businesses that exchange supplier files across different systems, including accounting platforms that use GST-inclusive prices.
Hardening The Import Workflow
A CSV extension alone does not prove that a file is safe. Check the detected content, impose size and row limits, and reject unexpected columns when the schema must remain strict. Consider virus scanning when files come from external users.
Useful tests should cover quoted commas, embedded double quotes, blank lines, duplicate emails, invalid postcodes and UTF-8 characters. Include a date test for Australian notation and verify that a file exported from common spreadsheet software behaves as expected.
Practical Safeguards For Production Imports
- Confirm the multipart field name and reject missing files.
- Limit file size, row count and maximum column length.
- Validate headers before processing any records.
- Parse Australian dates and currency values explicitly.
- Return row numbers without exposing sensitive field values.
- Use a transaction for the persistence phase.
- Store an import audit record with status, timestamp and user identity.
These controls make the upload feature predictable for teams in Adelaide, Canberra and regional locations, where staff may still exchange spreadsheets as part of daily operations. They also provide a clear path from a simple OpenCSV parser to a monitored, maintainable import service.