4

I am trying to parse LocalTime from spring's application.properties with following code:

@Value("#{ T(java.time.LocalTime).parse('${app.myDateTime}')}")
private LocalTime myDateTime;

in application.properties I have defined property like this:

app.myDateTime=21:45:00

Error message:

Failed to bind properties under 'app.my-date-time' to java.time.LocalTime:

Property: app.my-date-time
Value: 21:45:00
Origin: class path resource [application.properties]:44:15
Reason: failed to convert java.lang.String to @org.springframework.beans.factory.annotation.Value java.time.LocalTime

Any idea what I did wrong? Thank you.

Error in debug mode:

Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.LocalTime] for value '21:45:00'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [21:45:00]
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191)
    at org.springframework.boot.context.properties.bind.BindConverter$CompositeConversionService.convert(BindConverter.java:170)
    at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:96)
    at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:88)
    at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:313)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:258)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214)
    ... 210 common frames omitted
Caused by: java.lang.IllegalArgumentException: Parse attempt failed for value [21:45:00]
    at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:206)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
    ... 217 common frames omitted
Caused by: java.time.format.DateTimeParseException: Text '21:45:00' could not be parsed at index 5
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    at java.time.LocalTime.parse(LocalTime.java:441)
    at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:72)
    at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:46)
    at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:200)
    ... 218 common frames omitted
Ryuzaki L
  • 37,302
  • 12
  • 68
  • 98
Denis Stephanov
  • 4,563
  • 24
  • 78
  • 174

4 Answers4

12

By default, Spring uses localized non-iso FormatStyle.SHORT for time formatting unless this is overridden somehow. So I suspect you could use 09:45 PM for the property value but this is not what you want.

But as I already mentioned, you can just globally override the pattern which is expected by Spring using the DateTimeFormatterRegistrar machinery.

Or you can also use the @DateTimeFormat annotation on your LocalTime myDateTime field. With this annotation, there are three ways you can specify the desired format, namely style, iso, pattern. In your situation, iso = DateTimeFormat.ISO.TIME would work. So your code will look like this:

@Value("${app.myDateTime}")
@DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
private LocalTime myDateTime;

And note, these three attributes are mutually exclusive (see javadoc at the link provided):

Each attribute is mutually exclusive, so only set one attribute per annotation instance

Dmitry Khamitov
  • 3,061
  • 13
  • 21
9

Option 1 - use @ConfigurationPropertiesBinding

If you use @ConfigurationProperties to load your properties you can use @ConfigurationPropertiesBinding to bind custom converters to Spring:

@Component
@ConfigurationPropertiesBinding
public class LocalTimeConverter implements Converter<String, LocalTime> {
  @Override
  public LocalTime convert(String source) {
      if(source==null){
          return null;
      }
      return LocalTime.parse(source, DateTimeFormatter.ofPattern("HH:mm:ss"));
  }
}

enter image description here

Option 2 - use @Value

If you rather stick to @Value then you were pretty close:

@Value("#{T(java.time.LocalTime).parse('${app.myDateTime}', T(java.time.format.DateTimeFormatter).ofPattern('HH:mm:ss'))}")

See https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html for a list of DateTimeFormatter options.

Sources:

Community
  • 1
  • 1
Babyburger
  • 1,730
  • 3
  • 19
  • 32
  • Second option is what I am looking for but I got same error with that: failed to convert java.lang.String to @org.springframework.beans.factory.annotation.Value java.time.LocalTime – Denis Stephanov Oct 23 '19 at 16:22
  • @DenisStephanov I was surprised to hear that, so I tried the first option myself. It does work. The only mistake in that answer is that you want "HH:mm:ss" so that you can parse hours in interval [0-23]. I updated the answer with the hour correction and a screenshot of how it looks like for me. Probably the second answer fails for the same reason. I linked the DateTimeFormatter documentation with the intention of you suiting the DateTimeFormatter to your needs. – Babyburger Oct 23 '19 at 22:12
  • I updated question with stack trace, please check it out – Denis Stephanov Oct 24 '19 at 06:23
  • 1
    Both options work like a charm, this answer should be marked as correct. – toowboga Oct 27 '22 at 09:34
1

To inject Dates into @Value, Spring Expression Language (SpEL) is used, for example:

@Value(“#{new java.text.SimpleDateFormat(‘${aTimeFormat}’).parse(‘${aTimeStr}’)}”)
Date myDate;

In your case, you are directly injecting the Date value without providing the formatter, so it doesn't knows what format to parse to , define a new property with the format and use that formatter to parse the value like above example, it will get injected. Your application.properties properties should be like:

aTimeStr=21:16:46
aTimeFormat=HH:mm:ss
  • 2
    Does this work with `LocalTime`? Because the OP is using `LocalTime`. – Slaw Oct 23 '19 at 15:50
  • I tried with DateTimeFormatter like this (java.time.format.DateTimeFormatter).ofPattern('HH:mm:ss') but not works. Error: Cannot convert value of type 'java.time.format.Parsed' to required type 'java.time.LocalTime': no matching editors or conversion strategy found – Denis Stephanov Oct 23 '19 at 16:03
0

I have tested this on spring boot-2.1.5 with jdk-11

@Value("#{ T(java.time.LocalTime).parse('${app.myDateTime}',T(java.time.format.DateTimeFormatter).ISO_LOCAL_TIME)}")
private LocalTime timeValue;   //21:45
Ryuzaki L
  • 37,302
  • 12
  • 68
  • 98