How would you get per-actor heaps that cannot be violated by other actors? That is critical to Erlang's ability to recover from processes dying. I spent a lot of time doing Java and can't think how you could (you could in the JVM if you had language constructs for it, but then we are back to a new language).
There's a reason Stackless Python's actors aren't just a library on top of Python.
But those hacks wouldn't provide the same guarantees that language-level changes provide. Sure, you can try not to impact other thread's heaps, but nothing is stopping me, which means a simple programming error has the potential to impact multiple threads. As a result, you can't just "reboot" that thread (a critical piece of what makes Erlang interesting), because you have no guarantees its errors didn't impact other threads. You also have no guarantees that the underlying libraries aren't mucking up all of your carefully crafted memory management.
It's like the kernel protecting memory so applications can't overwrite each other. Sure, applications could just write to their own memory, but nobody actually trusts that model[1]. Instead, they want something below that level enforcing good behavior.
1. Obviously, virtual memory adds a wrinkle to this that kind of forces kernel protection, but even if we had literally unlimited RAM, we would still implement kernel protections on memory.
Erlang itself is, after all, implemented in C and ASM.
But what you can't (practically) implement yourself in other languages is all the professional care and maturity that have gone into the whole package over its long history. AFAICT, Erlang/OTP is much more than just a library.
Ultra-lightweight actors, a VM scheduler tuned for scheduling massive numbers of concurrent ops, etc. If you tried doing this in with a java library running java code, you couldn't get anywhere near their level of concurrency.
Obviously, you can do the same things in Java, as people have demonstrated with alternative languages that target the JVM.
It's the expressiveness at the language level that is really the "magic". For example, doing the equivalent of OO is not intuitive in Erlang, but completely possible (actually easy, but it looks...wrong) whereas it's supported by every Java tool. By the same token, pattern-matched message passing, lightweight green threads, and hot code deployment are primary concepts in Erlang.
You can at best implement a mimicry of Erlang's message passing in Java.
With sufficient effort, you can have the equivalent of no shared mutable data.
What you cannot have is completely separate heaps, so that if one thread crashes for whatever reason it doesn't take your application down.
Also, good luck trying to find a garbage collector that supports completely separate heaps that isn't a direct copy/near-identical implementation of the BEAM[1] VM's GC.
[1] The virtual machine that is the stock VM for Erlang. (In fact I don't know of any others but I have never looked.)
We've been doing it in Java since the nasty old days of RMI or "our own weird RPC over HTTP implementation". Lots of lightweight services with a supervisor to manage them and a directory to find them.
Now it's even easier with ESBs. Write a ten-line grails service to expose a bin-packing facility with Drools and then never touch it again.
Erlang's core concept of concurrency seems like something that'd be better suited as a library and app server than a whole language and runtime.
I've yet to hear of any Erlang-specific magic that cannot be implemented inside another language.