35

I need to define Duration value (spring.redis.timeout) by application.properties.

I was trying to use one point defined in Spring boot documentation:

Spring Boot has dedicated support for expressing durations. If you expose a java.time.Duration property, the following formats in application properties are available:

A regular long representation (using milliseconds as the default unit unless a @DurationUnit has been specified) The standard ISO-8601 format used by java.util.Duration A more readable format where the value and the unit are coupled (e.g. 10s means 10 seconds)

When i use spring.redis.timeout=3s Spring boot application throws this exception:

Cannot convert value of type 'java.lang.String' to required type 'java.time.Duration': no matching editors or conversion strategy found

Which would it be the best way to set a correct value to a Duration property in application.properties withs last Spring boot 2 release?

Alien
  • 15,141
  • 6
  • 37
  • 57
CRISTIAN ROMERO MATESANZ
  • 1,502
  • 3
  • 14
  • 26
  • https://stackoverflow.com/questions/47734728/not-able-to-use-java-util-duration-as-value-configuration-in-spring-boot – pvpkiran Aug 13 '18 at 08:56
  • So, according to the spring-boot github. It is not possible to use Duration and @Value for example. They were expecting this for spring-boot 2.1.0: https://github.com/spring-projects/spring-boot/issues/13237 – Brother Aug 13 '18 at 08:59
  • I have posted solution to be able to use @value. – CRISTIAN ROMERO MATESANZ Aug 13 '18 at 13:19
  • https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1.0-M3-Release-Notes. – Wilder Valera Sep 28 '18 at 20:02
  • Please refer my solution https://stackoverflow.com/questions/67722926/spring-boot-property-value-conversion-from-string-to-duration-works-in-applicati/67726164#67726164 – chandra kumar May 27 '21 at 16:22

7 Answers7

43

Any property which is of type duration can be injected via .properties or .yml files. All you need to do is use a proper formatting.

If you want to inject a duration of 5 seconds it should be defined as PT5S or pt5s or PT5s

  • case of the letters doesn't matter, so you use any combination which is readable for you
  • generally everyone uses all capital letters

Other examples

PT1.5S       = 1.5 Seconds
PT60S        = 60 Seconds
PT3M         = 3 Minutes
PT2H         = 2 Hours
P3DT5H40M30S = 3Days, 5Hours, 40 Minutes and 30 Seconds

You can also use +ve and -ve signs to denote positive vs negative period of time.

  • You can negate only one of the entity for example: PT-3H30M = -3 hours, +30 minutes, basically -2.5Hours
  • Or You can negate the whole entity: -PT3H30M = -3 hours, -30 minutes, basically -3.5Hours
  • Double negative works here too: -PT-3H+30M = +3 Hours, -30 Minutes, basically +2.5Hours

Note:

  • Durations can only be represented in HOURS or lower ChronoUnit (NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS) since they represent accurate durations
  • Higher ChronoUnit (DAYS, WEEKS, MONTHS, YEARS, DECADES,CENTURIES, MILLENNIA, ERAS, FOREVER) are not allowed since they don't represent accurate duration. These ChronoUnits have estimated duration due to the possibility of Days varying due to daylight saving, Months have different lengths etc.
  • Exception - Java does automatic conversion of DAYS into HOURS, But it doesn't do it for any other higher ChronoUnit (MONTHS, YEARS etc.).

If we try to do a "P1D", java automatically converts it into "PT24H". So If we want to do duration of 1 MONTH, we will have to use PT720H or P30D. In case of P30D java's automatic conversion will take place and give us PT720H


Upvote, if it works for you or you like the explanation. Thanks,

Rituraj
  • 685
  • 6
  • 6
  • 29
    Spring Boot also supports ["simple" duration formatting](https://docs.spring.io/spring-boot/docs/2.1.12.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-conversion-duration): "60s", "5m", "2h". – M. Justin Dec 18 '20 at 17:43
  • Just make sure you don't have any trailing spaces. – Vinagy Feb 26 '21 at 09:04
  • 1
    This is not working for me with Spring Boot 2.3.2.RELEASE w java 11. Getting IllegalStateException "Cannot convert value of type 'java.lang.String' to required type 'java.time.Duration': no matching editors or conversion strategy found" Usage: @Value("${foo.start.window:PT2H}") private Duration fooStartWindow; – JohnC May 15 '21 at 23:44
  • @JohnC - I believe you are using it wrong. can you paste code snippet here? Technically the code would be `@Value("${foo.start.window:startWinPeriod}") private Duration fooStartWindow;` and in your app property, you will have something like `foo.start.window.startWinPeriod=PT2H` Or yml file like below `foo: start: window startWinPeriod:PT2H` – Rituraj May 17 '21 at 20:41
  • Case does matter in some cases. ISO-8601 requires upper case https://en.wikipedia.org/wiki/ISO_8601#Durations, so that is what everybody should use. Java's Duration implementation is relaxed about it, but some other implementations aren't, including Spring Boot 2.4.3's DurationStyle.ISO8601, which requires an upper case P. – malamut Feb 01 '22 at 12:54
  • Beware that floating point numbers only work on the seconds field. So as in @rituraj 's examples, you need to say PT3H30M, and cannot abbreviate it to PT3.5H . – malamut Feb 01 '22 at 13:00
  • Thanks, how to represent 6 months ? P6M gives this error "'P6M' is not a valid ISO-8601 duration" – TwiToiT Mar 18 '22 at 10:02
  • P6M would throw an exception since Durations can only be represented in Hours or lower chronounits. Higher chronounits i.e. Days, Weeks, Months,... have estimated duration due to the possibility of daylight saving time changes. So if you try to do a "P1D", you will actually get "PT24H". Java takes care of Days for us, but doesn't take care of months. So in your case for 6Months of duration, you can do PT4320H or P180D – Rituraj Mar 25 '22 at 21:04
19

Update for Spring Boot 2.5.5


We can use @Value annotation together with application.properties values. For example you have the next property in your application.properties file:

your.amazing.duration=100ms

Then you can use it in the @Value annotation:

@Value("${your.amazing.duration}") 
final Duration duration;

That is all.


Supported units:

  • ns for nanoseconds
  • us for microseconds
  • ms for milliseconds
  • s for seconds
  • m for minutes
  • h for hours
  • d for days

Docs: link

Volodya Lombrozo
  • 2,325
  • 2
  • 16
  • 34
16

It's possible to use @Value notation with Spring Expression Language

@Value("#{T(java.time.Duration).parse('${spring.redis.timeout}')}")
private Duration timeout;
Serge
  • 201
  • 2
  • 3
  • 4
    Spring Boot now supports Duration directly (without using SEL). Tested on Spring Boot 2.1.9.RELEASE. – Jardo Oct 16 '19 at 14:35
9

The Duration in the moment (Spring-Boot 2.0.4.RELEASE) it is not possible to use together with @Value notation, but it is possible to use with @ConfigurationProperties

For Redis, you have RedisProperties and you can use the configuration:

spring.redis.timeout=5s

And:

@SpringBootApplication
public class DemoApplication {

  @Autowired
  RedisProperties redisProperties;

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

  @PostConstruct
  void init() {
    System.out.println(redisProperties.getTimeout());
  }
}

It printed (parse as 5s):

PT5S

https://docs.oracle.com/javase/8/docs/api//java/time/Duration.html#parse-java.lang.CharSequence-

CRISTIAN ROMERO MATESANZ
  • 1,502
  • 3
  • 14
  • 26
Brother
  • 2,150
  • 1
  • 20
  • 24
4

If your Spring-Boot version or its dependencies don't put ApplicationConversionService into context (and Spring-Boot doesn't until 2.1), you can expose it explicitly

@Bean
public ConversionService conversionService() {
    return ApplicationConversionService.getSharedInstance();
}

It invokes Duration.parse, so you may use PT3S, PT1H30M, etc in properties files.

Max
  • 41
  • 1
3

Spring Boot attempts to coerce the external application properties to the right type when it binds to the @ConfigurationProperties beans. If you need custom type conversion, you can provide a ConversionService bean (with a bean named conversionService)

See: https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-external-config-conversion

Create new ApplicationConversionService bean (it must be named conversionService ). Here you are my code tested with Spring boot 2.0.4:

@Configuration
public class Conversion {

@Bean
public ApplicationConversionService conversionService()
{
    final ApplicationConversionService applicationConversionService = new ApplicationConversionService();
    return applicationConversionService;
}

Here you are an example project using this approach:

https://github.com/cristianprofile/spring-data-redis-lettuce

CRISTIAN ROMERO MATESANZ
  • 1,502
  • 3
  • 14
  • 26
  • 2
    in 2.1.x this wont be needed. see https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1.0-M3-Release-Notes – Wilder Valera Sep 28 '18 at 20:02
  • I will try Context ApplicationConversionService Support (2.1) The ApplicationConversionService is now registered by default with the Environment and BeanFactory created by SpringApplication. This allows you to use application converters directly with core Spring Framework items such as the @Value annotation: @Value("${my.duration:10s}") private Duration duration; – CRISTIAN ROMERO MATESANZ Oct 01 '18 at 07:58
1

I was getting this error, but only during testing; the bean using a @Value-annotated Duration was otherwise working. It turned out that I was missing the @SpringBootTest annotation on the test case class (and the spring-boot-test dependency that provides it) and that was causing only a subset of standard converters to be available for use.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Not necessary. In my case Duration was a fallback, original value is `Long`, some convertors might be missing, adding `PT` prefix solved the problem: `@Scheduled(fixedDelayString = "${app.batchFreq:PT10m}")` – gavenkoa Sep 20 '22 at 13:11
  • I also get this error only during testing, the solution I found so far is to load a `conversionService` bean like this in the test context: `@Bean public ConversionService conversionService() { return ApplicationConversionService.getSharedInstance(); }` – vortex.alex Nov 21 '22 at 08:46