Blocking (as in Blocking IO) in the sense of IO means that the thread which initiated the IO goes to sleep until the IO result is available.
Non-Blocking IO (or Asynchronous IO) tells the relevant driver (the kernel, a DB driver, etc.) to initialize an IO action and then the thread keeps on doing other stuff. depending on the technology you use, you handle asynchronous IO results (which may be even an exception) in callbacks (such in Node.js) , channels (Java) , futures (C++) , promises (newer versions of Node.js), Tasks (.Net), coroutines(C++17) etc.
Asynchronous IO does not use threads to make the IO asynchronous. this is the key point here. throwing a blocking IO action to the thread-pool will not make it asynchronous, its just blocking on another thread, and very non scalable. it will make the threadpool be drained out of threads because they will just block as more and more blocking IO is submitted. as they write in their documentation:
The most common place where a typical Play application will block is
when it’s talking to a database. Unfortunately, none of the major
databases provide asynchronous database drivers for the JVM
meaning that most of the DB implementation do no provide asynchronous IO for Java - throwing a SQL query to a thread-pool will make the thread-pool thread block. this is what they mean by saying :
Note that you may be tempted to therefore wrap your blocking code in
Futures. This does not make it non-blocking, it just means the
blocking will happen in a different thread.
as we said before , Asynchronous IO is not Blocking IO on another thread.
An example for a Real asynchronous IO in Java is provided by the standard package java.nio