If your program requires 5 OS threads to be able to make progress (say, there are operations for which 4 threads are blocked while the 5th does work), and the runtime only spawns 2 OS threads and tries to schedule your 5 now virtual threads on those 2 OS threads, then your old program was perfectly correct and the virtual threads runtime has broken it.
This was a known and advertised failure case with the new threads runtime, exacerbated by limitations in the implementation that cause certain blocking operations to block the current OS thread instead of blocking the virtual thread and allowing another virtual thread to be re-use the underlying OS thread.
Having X resources and Y dedicated threads that operate on them, where Y > X, and allowing a thread to block in a way that requires the assistance of another thread to make progress but only when it's holding a resource, is a perfectly reasonable, standard, and safe design. When a change to the runtime silently reduces the number of synchronized sections a program can enter concurrently, it's not at all surprising that this breaks working code.