After contemplating this for a while I do agree that Thread.sleep is the straight forward way to stall the main thread in a loop obviously.
In other use cases that may be differnt, though, since, well, it stalls a thread that could do something else in the meantime. Here you have a discussion of Timer vs Thread.sleep, if you're interested and here another somewhat less comprehensive answers for a question that could pass as a duplicate.
public static void sleep(int someDelay) {
   try {
      Thread.sleep(someDelay);
   } 
   catch (InterruptedException e) {
      Thread.currentThread().interrupt();
   }
}
For more demanding scenarios ScheduledThreadPoolExecutor is your friend.
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(poolSize);
    executor.schedule(() -> {
        // doSmthg
    }, 12, TimeUnit.HOURS);
Another way to go was Timer/Timertask, which is now usually discouraged (thx to @rkosegi for pointing this out) in favor of ScheduledThreadPoolExecutor. A poolsize of 1 mimicks Timer/Timertask functionality.
On the other hand Timer/Timertask has the upper hand for large numbers of small tasks in a single thread scenario "Implementation note: This class scales to large numbers of concurrently scheduled tasks (thousands should present no problem). Internally, it uses a binary heap to represent its task queue, so the cost to schedule a task is O(log n), where n is the number of concurrently scheduled tasks.", where ScheduledExecutorService uses a BlockingQueue.
new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            // do smthg;
        }
    }, someDelay);