Six Java features to stay away from
I have spent countless hours troubleshooting different applications. Via the experience I can draw a conclusion about several Java SE features/APIs which most of the developers should just stay away from. When I refer to most of the developers, I have the regular Java EE developers in mind, not to the library designers / infrastructure engineers.
Full disclosure: I do honestly think that in the long run, most of the teams are better off staying away from the following features. But as always, there are exceptions. If you have a strong team and are fully aware of what you are doing, go ahead. In most cases though, if you start including following tools to your arsenal, you will regret it in the long run:
- Reflection
- Bytecode manipulation
- ThreadLocals
- Classloaders
- Weak/Soft references
- Sockets
But enough of the intro, let me go through the list of the warning signs backed with the explanation of the underlying problems:
Reflection. In popular libraries such as Spring and Hibernate, the reflection has its place. But introspecting a business code is something that is bad in so many reasons that I almost always recommend to avoid it:
First comes the code readability/tooling support. Open up your favourite IDE and find inter-dependencies in your Java code. Easy, isn’t it? Now, replace the method calls with reflection and try to repeat the process. Things go even more out of hand when you start modifying the state you should normally have encapsulated away. If you need an example, take a look at the following code:
public class Secret { private String secrecy; public Secret(String secrecy) { this.secrecy = secrecy; } public String getSecrecy() { return null; } } public class TestSecrecy { public static void main(String[] args) throws Exception { Secret s = new Secret("TOP SECRET"); Field f = Secret.class.getDeclaredField("secrecy"); f.setAccessible(true); System.out.println(f.get(s)); } }
Then you are about to miss compile-time safety. Seeing the same example above you can already see that making a typo in getDeclaredField() parameter is only discovered during runtime. As you might recall, discovering runtime bugs is a lot more tricky than getting rejected by your build script.
And last, there will be overhead. Reflection calls are optimized differently by the JIT. Some optimizations take longer to apply and some even cannot be applied. So the performance penalties on reflection can, sometimes, be orders of magnitude. But on a typical business application – you will not really notice the overhead, so this one is definitely a lesser of the evils.
To summarize, I can state that the only reasonable (indirect) reflection usage in you business code is via AOP. Other than this, you are better off staying away from the reflection.
Bytecode manipulation. If I see you are using CGLIB or ASM directly your Java EE application code, I feel like I immediately want to run away. Take the reasons I elaborated in reflection block, multiply the impact by five and you might start feeling the pain.
What makes things worse here is that you do not have the executable code anywhere in sight during the compile-time. Essentially, you do not know what code is in fact running in your production. So when facing troubles you are thrown into runtime troubleshooting and debugging – which – if you agree with me is a “bit” more time-consuming.
ThreadLocals. There are two unrelated reasons why seeing ThreadLocals in a business code makes me shiver. First, with the help of ThreadLocals, you could start feeling the temptation to use the variables without explicitly passing them down through the method invocation chain. Which could be useful on certain occasions. But when you are not careful, I can guarantee that you will end up creating lots of unexpected dependencies within your code.
The second reason is related to my day-to-day work. Storing data in ThreadLocals builds a highway to memory leaks. At least one out of ten permgen leaks I face is caused by extensive ThreadLocal usage. In conjunction with the classloaders and thread pooling, the good old “java.lang.OutOfMemoryError:Permgen space” is just around the corner.
Classloaders. To start with, the classloaders are a complex beasts. You must first understand them, the hierarchy, delegation mechanics, class caching, etc. And even if you think you have gotten it, the first (ten?) attempts will still not work properly. At minimum you will end up creating a classloader leak. So I can only recommend leaving this task to application servers.
Weak and Soft References. Just learned what they are and how do they work? Good. Now you understand Java internals a bit better. Got that brilliant idea of rewriting all your caches with soft references? Not good. I do know that being equipped with a hammer makes you look around for a nail.
Why I think caching is not a good nail for such a hammer here you might wonder. After all, building a cache upon soft references could be a good example of how to delegate some of the complexities to the GC instead of implementing it yourself.
Let us take an arbitrary cache as an example. You build it using soft references for values so that when memory is exhausted the GC can step in and start cleaning. But now you do not have control over which objects were removed from the cache and are more than likely to recreate them on next cache-miss. If memory is still scarce you trigger the GC to clean them again. I guess you can see the vicious circle forming and your app becoming CPU bound with Full GC constantly running.
Sockets. The plain-old java.net.Socket is just too tricky to get right. I do think it is fundamentally flawed due to its blocking nature. When writing a typical Java EE application with a web-based front-end you need high degree of concurrency to support your numerous users. What you now do not want to happen is to have your not-so-scalable-at-all thread pool sit there waiting for blocked sockets.
There are wonderful 3rd party libraries available for the task at hand though, so instead of trying to get things right yourself, go and grab Netty.
Nice article. I agree on 5 of your 6 points. I disagree on Socket, though. It is a fundamental building block, which indeed shouldn’t be used directly in a Java EE application. As thread management, and your other 5 points, by the way (see “Programming restrictions” in Java EE specs, EJB and others).
I agree at certain degree from biz logic perspective, but in general 1, 2, 4, 5 is for code organization – core of platform, 3 – concurrency, you may want to have shared heavy resource instantiated once per thread context, why not? 5 – sockets, so this is the core component consumed by higher level components, how you would organize custom protocol?
Bad understanding, overuse, poor testing – are the main reasons of not using all of the mentioned features
+10000