Issue
I don't understand, what is wrong here? The date seems correct to me:
java.time.format.DateTimeParseException: Text '1971-04-29T00:00:00' could not be parsed at index 2
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:492)
at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:75)
at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:46)
...
If I just run this code, it works, so I assume it is some kind of Spring problem:
LocalDateTime.parse("1971-04-29T00:00:00")
The corresponding spring controller:
@Controller
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class RecordController(
val recordRepository: RecordRepository
) {
@PostMapping("/records")
@ResponseBody
@ResponseStatus(value = HttpStatus.CREATED)
fun createRecord(
@RequestBody
record: RecordEntity
): RecordEntity {
return recordRepository.save(record)
}
@GetMapping("/records")
@ResponseBody
fun getRecords(
@RequestParam("from", required = false)
from: LocalDateTime?,
@RequestParam("to", required = false)
to: LocalDateTime?,
@RequestParam("client-type", required = false)
clientType: ClientType?
): Iterable<RecordEntity> {
var spec: Specification<RecordEntity> = where(null)!!
if (from != null)
spec = spec.andFrom(from)
if (to != null)
spec = spec.andTo(to)
if (clientType != null)
spec = spec.andClientTypeSpec(clientType)
return recordRepository.findAll(spec)
}
fun Specification<RecordEntity>.andFrom(from: LocalDateTime): Specification<RecordEntity> {
return and { root: Root<RecordEntity>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
criteriaBuilder.greaterThan(root.get<LocalDateTime>("createdAt"), from)
} ?: this
}
fun Specification<RecordEntity>.andTo(to: LocalDateTime): Specification<RecordEntity> {
return and { root: Root<RecordEntity>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
criteriaBuilder.lessThan(root.get<LocalDateTime>("createdAt"), to)
} ?: this
}
fun Specification<RecordEntity>.andClientTypeSpec(clientType: ClientType): Specification<RecordEntity> {
return and { root: Root<RecordEntity>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
criteriaBuilder.equal(root.get<ClientType>("clientType"), clientType)
} ?: this
}
}
The Entity Object:
@Entity
@Table(name = "RECORDS")
class RecordEntity(
@Id @Column(name = "BOOKING_ID")
var bookingId: String,
@Column(name = "CLIENT")
@Enumerated(EnumType.STRING)
var clientType: ClientType,
@Column(name = "CREATED_AT", columnDefinition = "TIMESTAMP")
var createdAt: LocalDateTime
)
The Repository is rather boring:
@Repository
interface RecordRepository : CrudRepository<RecordEntity, String>, JpaSpecificationExecutor<RecordEntity> {
}
The Rest call:
curl --location --request GET 'localhost:8080/records?to=1971-04-29T00:00:00'
I'm not sure where the problem comes from, so I included all the code. Thanks for helping :)
Solution
Spring has a number of different conversion utilities for JDK types, like numbers and dates.
Specifically, Spring Boot, in order to setup the Spring MVC web infrastructure, uses DateTimeFormatterRegistrar
to register a bunch of different formatters for the date types in java.time
.
As of this writing, for a LocalDateTime
, it uses
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
For my system's Locale
, this DateTimeFormatter
produces formats like
12/19/20, 8:45 PM
From your error,
Text '1971-04-29T00:00:00' could not be parsed at index 2
I assume it does the same for parsing: index 2 would've been the position of the /
.
There are a number of ways to fix this, like registering your own DateTimerFormatterRegistrar
. The easiest solution, in my opinion, is to simply annotate your parameter with an appropriate @DateTimeFormat
. For example,
@RequestParam @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime to
This will force Spring MVC to use a separate DateTimeFormatter
instance, capable of parsing your date time string.
Answered By - Sotirios Delimanolis