Actually, the question of throwing IllegalArgumentException or NullPointerException is in my humble view only a "holy war" for a minority with an incomlete understanding of exception handling in Java. In general, the rules are simple, and as follows:
- argument constraint violations must be indicated as fast as possible (-> fast fail), in order to avoid illegal states which are much harder to debug
- in case of an invalid null pointer for whatever reason, throw NullPointerException
- in case of an illegal array/collection index, throw ArrayIndexOutOfBounds
- in case of a negative array/collection size, throw NegativeArraySizeException
- in case of an illegal argument that is not covered by the above, and for which you don't have another more specific exception type, throw IllegalArgumentException as a wastebasket
- on the other hand, in case of a constraint violation WITHIN A FIELD that could not be avoided by fast fail for some valid reason, catch and rethrow as IllegalStateException or a more specific checked exception. Never let pass the original NullPointerException, ArrayIndexOutOfBounds, etc in this case!
There are at least three very good reasons against the case of mapping all kinds of argument constraint violations to IllegalArgumentException, with the third probably being so severe as to mark the practice bad style:
(1) A programmer cannot a safely assume that all cases of argument constraint violations result in IllegalArgumentException, because the large majority of standard classes use this exception rather as a wastebasket if there is no more specific kind of exception available. Trying to map all cases of argument constraint violations to IllegalArgumentException in your API only leads to programmer frustration using your classes, as the standard libraries mostly follow different rules that violate yours, and most of your API users will use them as well!
(2) Mapping the exceptions actually results in a different kind of anomaly, caused by single inheritance: All Java exceptions are classes, and therefore support single inheritance only. Therefore, there is no way to create an exception that is truly say both a NullPointerException and an IllegalArgumentException, as subclasses can only inherit from one or the other. Throwing an IllegalArgumentException in case of a null argument therefore makes it harder for API users to distinguish between problems whenever a program tries to programmatically correct the problem, for example by feeding default values into a call repeat!
(3) Mapping actually creates the danger of bug masking: In order to map argument constraint violations into IllegalArgumentException, you'll need to code an outer try-catch within every method that has any constrained arguments. However, simply catching RuntimeException in this catch block is out of the question, because that risks mapping documented RuntimeExceptions thrown by libery methods used within yours into IllegalArgumentException, even if they are no caused by argument constraint violations. So you need to be very specific, but even that effort doesn't protect you from the case that you accidentally map an undocumented runtime exception of another API (i.e. a bug) into an IllegalArgumentException of your API. Even the most careful mapping therefore risks masking programming errors of other library makers as argument constraint violations of your method's users, which is simply hillareous behavior!
With the standard practice on the other hand, the rules stay simple, and exception causes stay unmasked and specific. For the method caller, the rules are easy as well:
- if you encounter a documented runtime exception of any kind because you passed an illegal value, either repeat the call with a default (for this specific exceptions are neccessary), or correct your code
- if on the other hand you enccounter a runtime exception that is not documented to happen for a given set of arguments, file a bug report to the method's makers to ensure that either their code or their documentation is fixed.