Welcome! Please see the About page for a little more info on how this works.

+16 votes
in Clojure by
edited by

From JDK19+ onwards, the following becomes reflective without adding a type hint:

(def x 10)
(Thread/sleep x)

The tricky bit is that in earlier JDKs the above did not reflect, but when you upgrade your JDK, the reflection starts.

If using native-image, your program may start to fail due to missing reflection configuration.

It would be nice to have a built-in sleep function that accepted a number so we don't forget to add type hints and it would work consistently across JDKs.


(ns clojure.java.misc)
(defn sleep [x]
  (if (number? x)
    (Thread/sleep (long x))

A better name for clojure.java.misc is welcome. Maybe just clojure.java or put this function into clojure.core?

Weirdly enough, now I can't reproduce it on 1.11.1 with JDK 20, at all - in REPL, by executing a script, by executing a `-main` function, with extra wrappers around both the var and the function.
(~/clojure)-(!2001)-> clj
    Clojure 1.12.0-alpha4
    user=> (System/getProperty "java.version")
    user=> (set! *warn-on-reflection* true)
    user=> (def x 1000)
    user=> (Thread/sleep x)
     Reflection warning, NO_SOURCE_PATH:1:1 - call to static method sleep on java.lang.Thread can't be resolved (argument types: unknown).
The "normal" thing that should happen in this case is that the call should continue to work, but reflectively, and you can correct (if you care) with ^long type hint. However, there was a subtle bug in the Reflector such that it never matched a smaller boxed numeric argument as valid to pass to a method that takes a wider primitive param.

Here, if you pass a boxed Integer at runtime, it would match no methods at all for Thread.sleep(). Also useful to know is that Cheshire will emit boxed Integer objects when reading JSON if the value is in the integer range (there's a ticket about this in Cheshire).

Combining those cases, the result we've seen is that if using Cheshire (or something else with this behavior that reads config files possibly as Integer), and upgrading to newer Java, can result not in deprecation, but a reflection error at runtime. That seemed unacceptable, so we filed https://clojure.atlassian.net/browse/CLJ-2843 and fixed this in Clojure 1.11.3 and 1.12.0-alpha10.

1 Answer

+1 vote
There is a portable analog to Thread/sleep in core.async.
Seems overkill to pull in the whole of core.async if all you need is sleep, though.