The performance penalty of Class.forName when parsing JPQL and Criteria queries
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
While reviewing this Hibernate Pull Request, I stumbled on the HHH-10746 Jira issue. After doing some research, I realized that this issue was reported multiple times in the past 10 years:
In this post, I’m going to explain why this issue was causing performance issues, and how it got fixed.
Query parsing
Unlike native SQL queries, entity queries (JPQL/HQL and Criteria) are parsed to an Abstract syntax tree (AST) which is later translated to a native SQL query. During syntax tokenization, all expressions containing dots used to be checked so that Hibernate knows if the expression represents a Java constant. This was done by ReflectHelper as follows:
public static Object getConstantValue(
String name,
ClassLoaderService classLoaderService) {
Class clazz;
try {
clazz = classLoaderService.classForName(
StringHelper.qualifier( name )
);
}
catch ( Throwable t ) {
return null;
}
try {
return clazz.getField(
StringHelper.unqualify( name )
).get( null );
}
catch ( Throwable t ) {
return null;
}
}
If only valid Java constant expressions would pass through. However, this method was accessed for every dot expression, therefore for every alias or entity class name.
To illustrate it, consider the following entity query:
List<Post> posts = entityManager.createQuery(
"select distinct p " +
"from Post p " +
"left join fetch p.comments " +
"where p.title = :title", Post.class)
.setParameter("title", "High-Performance Java Persistence")
.getResultList();
When parsing this query, the getConstantValue method is called with the following arguments:
com.vladmihalcea.book.hpjp.hibernate.fetching.DistinctTest$Postp.commentsp.title
Therefore, even a trivial entity query can lead to unnecessary calls to Class.forName.
The cost of Class.forName
Class.forName is not free. Depending on the underlying application server, it can cause lock contention as illustrated by this Dimo Velev’s article. Pierre-Hugues Charbonneau has a similar article on this topic, which is a wonderful read as well.
This issue has mostly affecting WebLogic, as demonstrated by the aforementioned Hibernate Jira issues, as well as this StackOverflow question.
However, calling Class.forName with alias fragments or package paths, as it’s the case for our the entity query above, is just a waste of resources. Even if there is no lock contention, the ClassLoader needs to scan the current classpath, and that’s going to have an impact in a high-performance enterprise application.
The fix
There were several proposals for a fix. Some users have used a ConcurrentHashMap to store the outcome for each dot expression. However, this solution is more like a band-aid because it doesn’t really address the lock contention issue. Although the ConcurrentHashMap is optimized for a certain level of concurrency, it still uses locks at a partition level. To be effective, the ConcurrentHashMap would need to store a large number of entries, therefore putting pressure on memory. For these reasons, I looked for an alternative solution to address this issue.
To overcome this issue, I have added a verification that simply discards any expression that doesn’t follow the Java Naming conventions for a constant.
As long as you express all your Java constants using the fully qualified Class name and the constant field name contains only uppercase letters and underscores, Hibernate will locate it properly and will discard any other dot expression that should never go through a Class.forName API call.
public static Object getConstantValue(
String name,
SessionFactoryImplementor factory) {
boolean conventionalJavaConstants = factory
.getSessionFactoryOptions()
.isConventionalJavaConstants();
Class clazz;
try {
if ( conventionalJavaConstants &&
!JAVA_CONSTANT_PATTERN.matcher( name ).find() ) {
return null;
}
ClassLoaderService classLoaderService = factory
.getServiceRegistry()
.getService( ClassLoaderService.class );
clazz = classLoaderService.classForName(
StringHelper.qualifier( name )
);
}
catch ( Throwable t ) {
return null;
}
try {
return clazz.getField(
StringHelper.unqualify( name )
).get( null );
}
catch ( Throwable t ) {
return null;
}
}
When using Hibernate, stick to Java Naming conventions when for constant expressions.
If you’re using non-conventional Java constants, then you’ll have to set the hibernate.query.conventional_java_constants configuration property to false. This way, Hibernate will fall back to the previous behavior, treating any expression as a possible candidate for a Java constant.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
This HHH-4959 is included in Hibernate ORM 5.2.6. If you have any performance-related issue, don’t hesitate to contact me. One of my goals as a Hibernate Developer Advocate is to make sure we tackle all issues that can hurt application performance.
Stay tuned! Hibernate ORM 6.0 comes with a new Query Parser (SQM) which should address this issue at the parser level.





