I recently came across a very interesting talk from Pavlo Baron entitled What can be done with Java but should better be done with Erlang. Pavlo discusses several scenarios in which Erlang should be preferred over a Java-based solution. He does not succumb to simple Java bashing, but gives adequate reasons why the Erlang-based pendant to a Java solution is advantageous. And since we talk about adequacy every now and then when it comes to software engineering, I can highly recommend to follow the above link and watch the talk for yourself.
Pavlo's comparison between Erlang and Java is - amongst others - based on several features of both languages and their respective runtimes. I have selected some of those points and like to share my thoughts on them.
Implementation of low-level protocols
Erlang supports an integrated pattern matching mechanism that also works on binary data. Pattern matching not only harmonizes well with Erlang's "Let it crash!"-principle, but is also quite powerful when implementing communication protocols that exchange binary data. Pattern matching is a great tool to destructure binary data into local variables. Together with Erlang's Bit syntax, which lets you serialize data, the language provides a powerful abstraction for handling incoming and outgoing binary data. Admittedly, this is not the typical use case a Java developer is confronted with, but nevertheless is the efficient handling of binary data in Java quite tedious and tends to be more error-prone, since the language itself does not support any built-in constructs like Erlang does.
Hot-Code-Swap
Hot code swapping is the ability to dynamically exchange a unit of compilation (e.g. module, class, JAR archive, ...) with a new version at runtime. This is a built-in concept in Erlang. If you use OTP (Open Telecommunications Platform), which is an open source distribution of Erlang along with lots of libraries and an application server, you even get direct support for state migration when swapping different versions of code. Of course, you can achieve hot code swapping in Java as well, but the approaches you can take here have certain limitations.
Possible Java solution: Use the class loader hierarchy
This is not as simple as it may sound, because a class loader assures that a certain class is not loaded twice. Re-loading a class is not supported in standard class loaders. A possible solution is to implement your own class loader and attach it to the existing hierarchy of class loaders. However, this approach is no solution for long-term operations, since it leads to the so called instance fatigue problem. Why is that? The contract of a class loader states that a class loader has to establish a 1:1-relationship with the classes it has loaded. If you have to support multiple reloads of a class, you need a new instance of your custom class loader every time you do so. And this is considered critical and you should not use such an approach in live applications. Actually, most of the application servers in the Java world don't recommend to enable hot code deployment of that kind of sort, because it bears the risk that the associated Java process will turn into a zombie process over time.
Possible Java solution: Use JVM Hot Swap
Unfortunately, this approach has rather harsh limitations. As you know, the JVM is optimizing constantly at runtime, which can lead to code-inlining of methods if it identifies hot spots in your code. Because of such optimizations, swapping code is only possible at the level of method implementations. However, most of the times you want to be able to swap at coarser-grained units of compilation, like whole classes or sets of classes contained in a JAR file. If you want to read on concerning this topic, you should definitely check out this article. It covers the problem with JVM Hot Swap in much more detail.
Possible Java solution: Use OSGi
OSGi was designed for embedded devices that run 24/7 and thus supports mechanisms like hot code swapping. But, OSGi is considered as being rather invasive and as far as I know does not support state migration out-of-the-box when you swap different versions of compilation units. Be aware of the fact that you will have additional efforts while migrating OSGi bundles if you do need a mechanism for state migration. I found a project report (German only) on that particular subject, if you are interested in reading on.
Error handling
Typically, an Erlang-based application consists of many Erlang processes running concurrently, where each process has a certain responsibility or task to carry out. Erlang processes don't have a 1:1-relationship with processes at the level of the operating system. They are implemented at the level of the Erlang runtime, manage their own address space in memory and are generally considered as being very lightweight. You can spawn hundreds of thousands of Erlang processes - even on moderate consumer hardware - without degrading the performance of your system heavily. Inter-process communication in Erlang is done via message passing. Processes don't share any state between them. Data is simply copied via message passing to other processes if need be. This design has the advantage that errors that occur in one process won't affect others. If a process runs into an error situation, you terminate it early rather than trying to perform some compensation logic that might turn the process back into a consistent state. Why bother? Erlang processes are typically arranged in a supervision tree, meaning that there are supervisor processes that get notified if a process dies. A supervisor can react on that event and take action by restarting the process. This is a very powerful design choice that came to be known as the so called "Let it crash!"-principle. It is so powerful that runtimes like Akka are based on this principle as well. The same level of resilience is much harder to achieve in plain Java, since inter-process communication is typically based on shared mutable state.
Parting thoughts
Though Erlang has some very good design choices that I personally miss in other languages dearly, it is no panacea. Erlang is a good candidate for technically oriented solutions like protocol implementations, middleware (RabbitMQ), databases (CouchDB) and servers of various natures. It is probably not the best fit if you need to implement complex business logic and therefore not your primary choice when constructing typical enterprise applications, unless your application has high demands regarding availability and resilience. The JInterface library lets you masquerade a Java program as an Erlang node, which makes mixed-scenarios, in which a Java client communicates with an Erlang server, at the very least possible. But the question whether such a mixed-scenario is beneficial yet remains to be answered.