We have a POJO in our Java application let's say Request which has validateRequest() method that basically validates some of it's fields using Guava's Preconditions library.
So, Basically Class looks like this :
public class Request {
private Integer field1;
private String field2;
.
.
private String fieldN;
public String validateRequest() {
try {
Preconditions.checkArgument( getField1() >= 0, "error message" );
Preconditions.checkArgument( StringUtils.isNotBlank(getField2()), "error message" );
.
.
} catch (IllegalArgumentException | NullPointerException e){
//emit some metric
return e.getMessage();
}
return null;
}
}
We call the below method in all the flows after building the Request POJO.
public void someMethod(Request request) {
String message = request.validateRequest();
if(message!= null)
throw new Exception(message);
}
In some of the flows in our application we are never initialising field1 and field2 , so they are always null in those flows. So, ideal expectation is that validateRequest should return error message for those flows. But, what we notice is production is that this application never returns error message. It is as if Preconditions are never executed! Even metric that we emit is zero. On searching web, found out that compiler's optimisation can cause certain validation/assertion statements to be ignored, but didn't get more details around it.
When we explicitly remote debug to our Production box and put break-point on precondition statements they get executed and error message is returned and metric is emitted.
When we deploy this same application is deployed on Stage, code behaves as expected and throws validation error and metric is emitted.
Want to understand if compiler optimisation is the culprit here, or if root cause could be something else. How to verify that compiler optimisation is causing Preconditions to be ignored?
This code seems to be a kind of Cargo Cult Programming. It’s not clear what you expect from the call to
Preconditions.checkArgument(…), but whatever it is, it’s not there.When you call
The expression
getField1() >= 0is evaluated in your method. You’ve already done the check before even calling thecheckArgumentmethod. All, the method does, is throwing a newIllegalArgumentExceptionif the argument isfalse, with the message you’ve provided, i.e.which you then catch, to extract the very error message, you have provided yourself.
So, you have created an inefficient way to do the same as
Since the evaluation of
getField1() >= 0happens in your method, before thecheckArgumentmethod is invoked, it will also fail with aNullPointerExceptioninstead of ever invoking thecheckArgumentmethod whengetField1()returnednull. This JVM generatedNullPointerExceptionwill have an implementation specific message or no message at all.This is not an optimization but just the way method invocations work. First, the arguments are evaluated, then, the method is invoked. If the argument evaluation throws an exception the method is not invoked.
It’s not clear how you could see this working with a
getField1()returningnullduring debugging, as it is impossible to passnullto a method expecting a primitive typeboolean. Maybe you are confusing it with the scenario ofgetField2()returningnull.Your second check uses the expression
StringUtils.isNotBlank(getField2())which handles thenullcase, asisNotBlankwill returnfalsewhen the argument isnull.You can fix the first check by handling the
nullcase, e.g.or, if you insist on creating and catching an exception, just to extract the very message you have specified yourself,