Clojure is currently my language of choice for new projects. The reasons probably won’t come as a surprise – among other things I love the expressive style, the level of productivity which enables you to “get things done”, the interactive development at the REPL, the meta-programming capabilities, the approach to concurrency and the various advantages of functional programming.
Yet in several recent projects, I’ve found myself using a polyglot mix of Clojure and Java.
Why would I subject myself to this?
Well it turns out that there are several good reasons that this can make sense:
- Re-using existing Java code – in some cases you have good, working code in Java that you want to use directly. It really doesn’t make much sense to rewrite in Clojure just for the sake of it. One of the attributes I really admire in the Clojure community is pragmatism, and re-using good code is nothing if not pragmatic.
- Existing skills – in my case I’ve been using Java for 15 years. Despite the fact that I much prefer Clojure as a language, I can often bang out simple stuff in Java extremely fast and with a high degree of correctness simply from knowing the Java language and tools so well. And hiring people with strong pre-existing Clojure skills is tough.
- Providing a Java API – particularly if you are writing library code, you may want to provide a Java API to other users rather than requiring them to use Clojure. If you want your library to be widely used (whether publicly or within an organisation), this is often a pragmatic choice. You can always provide a thin Clojure wrapper for use by the Clojure-enlightened.
- Performance – well written low-level Java code can be extremely fast, thanks to the excellent work of many great engineers over many years in optimising the Java compiler and JIT. While Clojure is constantly improving in terms of performance, I still think you are paying a 2-5x performance overhead on average (perhaps 1-2x if you really optimise primitive code, 10-25x if you are quite liberal with your use of higher order Clojure constructs such as lazy sequences). Furthermore, mutability is important for performance-sensitive code which isn’t really an idiomatic fit for Clojure. In a way, you can see Java as the default low-level language for the JVM in the same way that C is the default low level language for native code.
- Gradual migration of a code base – if you are a Java shop that wants to introduce Clojure but has a lot of existing legacy Java code, you probably aren’t going to want to embark on a full rewrite immediately. A much more sensible approach is to port the code piece by piece (perhaps starting with a well-defined proof of concept) and then porting the rest of the code base over time (using the very easy Clojure/Java interop to keep everything working together). This is a pragmatic strategy, but in the transition period you are going to have no option but to work on a mixed code base.
If for one or more of the above reasons you decide that you are going to mix Java and Clojure code, here are some tips and tricks I have learned:
- Make Clojure call Java – in general your dependencies should flow this way. You should normally be aiming to use Clojure for high-level or “glue” code and Java for lower-level code or library functions. The Java code is therefore likely to be more “stable” so it’s going to be much less painful if you let frequently-changing Clojure code depend on stable Java code. It’s also slightly easier to implement the interop this way (no need to mess around with gen-class, AOT compilation of Clojure and/or cryptic invocations of the Clojure compiler).
- Adopt immutability in Java – Clojure likes working with immutable things. As far as possible, you should try to adopt this style in your Java code. Java is actually pretty decent for writing simple, high performance immutable data structures (see java.lang.String, or Rich Hickey’s Java implementation of the persistent data structures used in Clojure). The more you embrace immutability in the Java side, the easier your interop with Clojure will be.
- Java interfaces make a good dividing line between Clojure and Java code. You can implement them either directly in Java or using reify in Clojure. Particularly useful if you might want to switch the language used for the implementation in the future as it minimises the risk of breaking downstream code.
- Use a Java IDE with a Clojure plugin – personally I use Eclipse with the excellent Counterclockwise plugin. This is important if you want to avoid context-switching between a Java IDE and an alternative Clojure environment.
- Embrace Maven - this works for managing both your Clojure and Java dependencies and builds. Use the clojure-maven-plugin then it is easy to do a single build that encompasses both your Java and Clojure code in a single project. Leiningen is certainly great if you are working in pure Clojure, but the tool support for Maven is much better if you are still working in a part-Java environment.
So far the this polyglot approach has served me well.
As Clojure matures I expect I’ll try to move more and more code into pure Clojure. But I’m pretty sure that there will be at least some Java in my various code bases for a long time yet. But that’s fine. Don’t fix what isn’t broken.