The only place where you may want to limit the number of threads is at the entry to the system, but since the semaphore is the construct for limiting the number of anything, you might as well just use that there, too. For example, if the number of requests currently being processed is too high (above the semaphore's number of leases), you don't accept new connections.
Standard concurrency tools like semaphores are capable of reducing the amount of work being done at the same time. You could also simulate classic thread pools by using concurrent lists/sets/etc to replicate traditional work queues, but you'd probably lose any advantage you'd gained from switching to green threads in the first place.
1. Managing throughput with virtual threads (it's in Clojure, but it's using the underlying Java APIs)
https://andersmurphy.com/2024/05/06/clojure-managing-through...
As other have said, this can be achieved with a semaphore and the bulkhead pattern. You can also limit the number of number of connections.
I would expect any "production ready" web server using virtual threads having some sort of limiting. That must be the case right?