Let's get started with a Microservice Architecture with Spring Cloud:
Regular Expression Support in HQL
Last updated: June 27, 2026
1. Overview
Hibernate 7.2 introduces first-class regular expression support in HQL via the like regexp operator. Before this addition, matching a string column against a non-trivial pattern meant writing a native query. This new operator delegates regex matching to the database engine, avoiding native queries and post-filtering in Java.
In this tutorial, we’ll walk through a small Spring Boot example that demonstrates like regexp. We’ll look under the hood to understand how Hibernate translates it to SQL and what happens when the underlying database doesn’t support regex.
2. Preparing a Simple Spring Boot Example
Before we can demonstrate the like regexp operator, we need a tiny project to run queries against. We’ll set up the dependencies, an entity, some seed data, the application properties, and a Spring Data repository. For simplicity, we’ll not show all the code here.
2.1. Dependencies
Our project uses Spring Boot 3.3 as its parent. Spring Boot 3.3’s BOM pins Hibernate to 6.5, so we override the version and a couple of its transitive dependencies that Hibernate 7 requires newer versions of:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.6.1.Final</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
Beyond that, we pull in the usual spring-boot-starter-data-jpa and the H2 driver, as we’ll use the H2 in-memory database to keep the demo self-contained.
2.2. The Entity
We’ll keep things minimal with a single-column entity:
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
// constructors, getters ...
}
2.3. Seed Data
We load test data through Spring’s data.sql mechanism. The values are short phrases designed to exercise regex patterns:
INSERT INTO message (content) VALUES ('quick brown fox jumps over');
INSERT INTO message (content) VALUES ('hello world from spring boot');
INSERT INTO message (content) VALUES ('Order 1234 shipped on Monday');
INSERT INTO message (content) VALUES ('Please call 555 today');
INSERT INTO message (content) VALUES ('ERROR connection refused remotely');
INSERT INTO message (content) VALUES ('warning low disk space detected');
2.4. Application Properties
The test profile (application-test.yaml) wires up H2 and asks Spring to run data.sql after Hibernate creates the schema:
spring:
datasource:
url: jdbc:h2:mem:testdb
...
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
defer-datasource-initialization: true
show-sql: true
...
sql:
init:
mode: always
The defer-datasource-initialization: true setting ensures that data.sql runs after the schema is created. It’s also worth mentioning that we enabled show-sql option to log translated SQL. This helps us understand how the like regexp operator works. We’ll discuss it later.
2.5. The Repository
The repository is the part we’ll actually be exercising. It declares a pair of HQL queries using the new like regexp/not like regexp operators:
public interface MessageRepository extends JpaRepository<Message, Long> {
@Query("select m from Message m where m.content like regexp :pattern")
List<Message> findByContentMatchingRegex(@Param("pattern") String pattern);
@Query("select m from Message m where m.content not like regexp :pattern")
List<Message> findByContentNotMatchingRegex(@Param("pattern") String pattern);
}
As always, we’ll use unit tests to demonstrate how each operator behaves.
3. Demonstration
In our defined repository methods, the pattern parameter accepts a full regex pattern. To find every message that contains at least one digit, we use .*\d+.*:
@Test
void givenContainsDigitRegex_whenLikeRegexp_thenReturnsMatching() {
List<Message> results = repository.findByContentMatchingRegex(".*\\d+.*");
assertThat(results)
.extracting(Message::getContent)
.containsExactlyInAnyOrder(
"Order 1234 shipped on Monday",
"Please call 555 today");
}
Similarly, if we pass the same regex to the findByContentNotMatchingRegex() method, we’ll get all rows that don’t contain any digits.
@Test
void givenContainsDigitRegex_whenNotLikeRegexp_thenReturnsNonMatching() {
List<Message> results = repository.findByContentNotMatchingRegex(".*\\d+.*");
assertThat(results)
.extracting(Message::getContent)
.containsExactlyInAnyOrder(
"quick brown fox jumps over",
"hello world from spring boot",
"ERROR connection refused remotely",
"warning low disk space detected");
}
If we run these two tests, they will pass. The same pattern parameter drives both variants, so we don’t need a separate regex for each side of the predicate. Next, let’s understand how Hibernate’s like regexp operator works under the hood.
5. Under The Hood
Having seen the operator in action, let’s look at how Hibernate actually implements it and where the portability boundaries lie.
5.1. How like regexp Is Translated
When Hibernate parses m.content like regexp :pattern, it doesn’t translate to a LIKE statement at all. Instead, it routes the predicate through a Semantic Query Model (SQM) function that each dialect registers under the name regexp_like. The default H2Dialect maps regexp_like(x, y) to H2’s regexp_like(x, y) SQL function, so if we check the log output of the example test above, we can see the translated SQL:
select m1_0.id, m1_0.content
from message m1_0
where regexp_like(m1_0.content, ?)
In other words, like regexp is syntactic sugar at the HQL level, while the heavy lifting happens in the database. Other dialects bind the same SQM function to whatever native construct they support, such as REGEXP_LIKE in Oracle, the ~ operator in PostgreSQL, REGEXP in MySQL, and so on.
Some dialects, for example, SQL Server prior to 2025, have no native regular expression function at all. In that case, the dialect simply doesn’t register regexp_like, and any HQL using like regexp fails at SQL execution time because Hibernate emits a call to a function the database doesn’t know.
5.2. The Portability Caveat
Because each dialect delegates to the database’s native regex engine, the flavor of regex we can use is whatever the underlying database supports. Let’s see a few examples:
- H2 uses java.util.regex (Perl-compatible, Matcher.find() semantics — no implicit anchoring).
- PostgreSQL uses POSIX regular expressions, which differ in subtle ways. For example, it has no \d shorthand without the embedded (?x) flag.
- Oracle uses POSIX with a few extensions and does not support \d either. We must write [[:digit:]] or [0-9] instead.
- MySQL (8.x) uses ICU regex, again with its own quirks.
So while the HQL stays portable, the patterns we feed into like regexp generally don’t. For example, a regex tested against H2 may fail or behave differently when the application is pointed at Oracle. If our application targets multiple databases, we should stick to a conservative regex subset.
6. Conclusion
Hibernate 7.2’s like regexp operator lets us express regex predicates directly in HQL and have them pushed down to the database without resorting to native queries. The HQL surface is portable across dialects, but the actual regex flavor depends on the underlying database engine.
As always, the full source code is available over on GitHub.
















