You're forgetting about OTP, Erlang's battle tested framework that covers about any edge case conceivable in a concurrent world. By themselves, you could probably produce hacky actors, pattern matching and hod code swap implementations in most dynamic languages (and on VMs like NET or JVM) nowadays, but it's the whole package that has remained unmatched so far.

Not to mention Erlang's live console and logging capabilities which are indispensable when it comes to diagnosing crashed services or inspecting live systems and gathering stats. The fact that Erlang's data structures are immutable allows OTP to give you a very detailed report containing the exact state before the crash and the message that led to it, while in Java and most other languages your best bet is just an exception trace - and then you would have to guess what exactly went wrong because you don't have the system state anymore and can't recreate it easily for that matter. You can imagine that this makes debugging an Erlang application a joy - at least as joyous as it can get.

One other important aspect of OTP are supervisor hierarchies - Erlang is built around the idea that if something can fail, it will - so systems should be designed in such a manner that failure in one component doesn't tear down the whole system. Services are carefully subdivided into autonomous submodules, and if a module crashes OTP will attempt to restart it and continue its operation if possible. Imagine a png conversion submodule crashing because of a faulty png header - in a Java system this would most probably throw an IndexOutOfBoundsException and down comes your whole backend. Erlang/OTP would just note that the converter has crashed and then restart it. It can do that because the converter doesn't affect or depend on some interconnected, global, "complected" state and thus can be easily started and stopped at will.

Of course, Erlang's solution to concurrency is no silver bullet - you can still create deadlocks if you aren't careful and you can still create global state by abusing named processes, but it makes a lot of things a lot, lot easier.

edit: one more thing - Erlang's hot code swap support is really one of the most beautiful things I've seen when it comes language features. Now, swapping code by itself isn't such a big problem - you can do this in C as well by just reloading a DLL or in Java by un/reloading a specific class - but the tricky part is about swapping state, and this is where most imperative languages fail hard.

Said otherwise, when you update your service, you not only want to update your code but also your data structures. For example, if you have a class/struct like this:

struct person {
string name;
int age;
};

and would like to add an additional field `ageString` to it, for whatever purposes, how would you go about doing this in a more conventional language? You would have to track all usages of `person` somehow, and then replace the in-memory representation with the new version while making sure that no thread is reading or writing data, then swap the code while being careful that no new code is reading old data or vice versa. In fact, in most systems I've worked on such a thing would be almost impossible.

Erlang's processes and immutable data structures allow you to do just that - and as painless as possible. You provide a function that converts old state to new state (in this case containing the field `ageString`), the new code which operates upon the updated data structures and let Erlang do the rest. All messages that come in while the swap is taking place will be queued up and delivered later.

Moreover, if the update introduces a bug, you can as easily downgrade as you did upgrade. And all this while your service is up and running!