Hibernate @Find generates the repository

Jakarta Data @Find on a Hibernate 7 repository. Parameter names must match fields. The processor generates the implementation. Id lookup, criteria, @Query, pagination.

I shipped a finder named findByTitleContaining . Next quarter someone renamed the title column. The method still compiled. The query died on a request that had worked the week before. Hibernate 7 can move that failure into the build. You put @Find on a method. The interface is @Repository . You don't implement the method. HibernateProcessor , the annotation processor that used to ship as hibernate-jpamodelgen , generates the class. Name the interface Library and you get Library_ . The method name is not the query The processor ignores the method name. It matches each parameter's name and type to a persistent field of the entity you return. Miss, and your build fails. That's the message I want. This is Jakarta Data. Hibernate implements it. You import jakarta.data.repository.Find . Hibernate's own tests sit that import next to Repository and OrderBy , same package. Open the javadoc on org.hibernate.annotations.processing.Find and you're on the older generator. I wouldn't mix the two in one interface. A StatelessSession backs the repository. An EntityManager doesn't. Entities come back detached. No dirty checking on flush. Lifecycle calls don't cascade. If you needed the persistence context to notice a field change, you're in a different model. An id parameter calls get() The smallest finder is a primary-key lookup. Your parameter name has to be the @Id field. id lookup, from the Hibernate repositories guide @Repository interface Library { @Find Book book(String isbn); } isbn is the @Id of Book , so the generated method calls StatelessSession.get() . No row, and the method throws EmptyResultException . The specification's way out is Optional . Hibernate also accepts @jakarta.annotation.Nullable on the method. The guide marks that one as an extension. missing row comes back empty @Find Optional<Book> book(String isbn); I use the same shape when the key is an application id. It's legal only when your entity field is named id and the type is UUID . If the field is applicationId , your parameter has to be applicationId too, unless you use @By . same rule, application id @Find Optional<JobApplication> application(UUID id); Any other field is a criteria match A parameter that names a non-id field becomes a criteria query. Equality. Not a scrap of the method name. title equality, then a like match @Find List<Book> books(String title); @Find List<Book> books(@Pattern String title); A List or an array parameter becomes an in condition. Hibernate's @Pattern turns that one argument into like . Several parameters are a conjunction, so every argument has to match its field. A _ in the parameter name walks an association, and publisher_name means publisher.name . I'd stop there. Once your query touches more than one entity, I write it with @Query . @OrderBy("title") looks like a string you'll regret. The processor still validates that text at compile time. You can also pass a Sort<Book> or an Order<Book> built from the Jakarta Data static metamodel, _Book.title.asc() . That class is _Book . The Jakarta Persistence metamodel is the other one, Book_ . I keep them apart on purpose. Rename the parameter without lying The awkward case is a list of isbns. The field is isbn , so a raw parameter can't be named isbns . @By points your parameter at the metamodel constant and keeps the check. parameter name and field name diverge @Find List<Book> books(@By(_Book.ISBN) String[] isbns); Query-by-method-name is still there, for people leaving Spring Data. The repositories guide says don't use it for new code. I wouldn't. findByTitleContainingIgnoreCase is how my column rename survived compilation. Pages, and a session you must not share A PageRequest that returns Page<Book> is offset and limit. End the order with the identifier, or two of your requests can disagree about what page two means. CursoredPage is the key-based alternative. The next page restricts on the last key instead of skipping rows. Offset pages can skip or repeat when the table changes between your calls. Key pages can drift from the page number. Pick the failure you can explain to your team. The repository doesn't remember the previous page. You pass the same filters and the same order every time. Don't share a StatelessSession across transactions. Quarkus already exposes one for your persistence unit. Elsewhere you provide it. If CDI is on the build path, the generated repository is @Dependent . If Spring is on the path, the guide says the processor emits @Component and obtains the session through ObjectProvider , because Spring doesn't proxy that contextual object the way CDI does. I haven't compiled that path against a Boot version for this note. In a test I write new Library_(statelessSession) and I'm done. What I would not put on @Find I wouldn't put a join on @Find . A projection record, an update, or a delete doesn't belong there either. Those go on @Query . Hibernate typechecks that HQL at compile time, parameters included. You can omit from when the entity is obvious from the return type. You can't omit it when the return type is a record you assembled in the select list. @SQL is the native escape hatch, and the processor won't validate it. A bad native query waits until runtime. That's the failure I was trying to leave behind. Inserts and updates are their own annotations. @Insert , @Update , @Delete , and @Save map to insert , update , delete , and upsert on the stateless session. None of them cascade. Forget to save an association and it stays forgotten. Hibernate 7 also lets a @Find method take a Restriction . The guide is plain about this. Jakarta Data 1.0 has no portable restriction API, and this one is native to Hibernate. The sample mixes Book_ for the restriction with _Book for the order. I wouldn't build my repository style on that mix until Data 1.1 standardizes it. Where I would start I'd start with one interface. One @Find by id, returning Optional . Pin the processor to the same Hibernate line as hibernate-core . When I read the stable guide on 8 October 2026, the Gradle example was org.hibernate.orm:hibernate-processor:7.4.12.Final , plus jakarta.data:jakarta.data-api and hibernate-core . Then I'd add one equality finder on a real field. The first query that needs a join or a select list moves to @Query . If the parameter name has to match the field, your method name can finally say what you mean. The build is what notices when the field moved. Sources Introducing Hibernate Data Repositories , stable guide. Processor example hibernate-processor:7.4.12.Final . @Find , StatelessSession.get() , EmptyResultException , Optional , @Nullable , @Pattern , @By , @OrderBy , pagination, Library_ . Checked 8 October 2026. Hibernate Processor : static metamodel, query validation, Jakarta Data repository implementation. HHH20221Repository in hibernate-orm: import jakarta.data.repository.Find with Repository and OrderBy . org.hibernate.annotations.processing.Find in the ORM 8 javadoc. Older generator style. Not the annotation this article uses.