Yes - I was building an inetd program, copying the logic from an ancient inetd.c. So I wanted to to answer a socket, fork, reopen STDIN as that socket, and then exec. I just boiled down a test case to show you the differences. Having said that it seems that reopen is a bit unloved on MRI too. MRI does achieve changing an object's type through skulduggery, but it seems to elicit that same pukey reaction from Ruby programmers as e.g. C++ programmers exhibit when they discover Ruby monkey-patching - everyone has their limits
The problem is surely in the pattern of representing an I/O descriptor as a single object, and also letting the user also call dup2() ? You just have to change the type of an object to support that properly. OR maybe an acceptable hack would be for IO to go into a "proxy mode" once you call reopen, i.e. once you call IO#reopen on a valid other IO object, it just turns into a proxy for the new one. So it won't be the new object but it will behave like it.
I'm not sure it's a particularly important use of anyone's time to "fix" this, and I know when I'm fighting an uphill battle - having read around it I think I'm lucky my code works on MRI! I can probably make it work portably by simply dropping down to FFI methods to do all my low-level reopens, and ignoring the object model as I'm about to exec() it all away.
A more explicit error in JRuby might save another programmer some head-scratching, e.g. just when you IO#reopen to an IO of a different type, throw "Yuck, do you realise what you're asking?" and link to this discussion