I studied this and continued for the past week or so, and while I still do not have a definitive answer, I have a plausible theory.
I tried the suggestions from the comments, and thanks to them I was able to narrow things down to one change, if I have a request:
"where LOWER(aca.l5_codpost) like '"+StringEscapeUtils.escapeSql("NG179GT".toLowerCase())+"'"
The query then uses the index and returns very quickly.
If, on the other hand, I have:
where LOWER(aca.l5_codpost) like :postcode query.setString("postcode", "NG179GT".toLowerCase());
Then it does not use an index.
The theory is that Sybase optimizes the query plan without content information :postcode , so it does not use an index. It does not recompile the query when it knows the content, therefore, it never uses the index.
I tried to get the index to use (index acadress_codpost_lower) , and that didn't make any difference.
I tried set forceplan off and set literal_autoparam off and didn't make any difference.
The only thing I can find that changes the behavior is to directly include the option in the query plan, as well as it as a parameter.
So, the work around embeds the parameter in the query string, although I would still like to know what is actually happening and solve the problem properly.