Hibernate 6/Native Image from scratch. No Spring Boot, No micronaut, No quarkus

Hello i trying to create a new gradle project from scratch. With no assist from any prebuild framework.

Just gradle standard project and hibernate dependencies. Using VS Code and java extensions

The full github project can be found in

I followed instruction from this url

I had java 17 with graalvm distro, in linux mint 22.1

Using sdkman

sdk install java 22.3.r17-grl

I can connect to database. Start a transaction, commit. But i cannot add annotated class model using the compiled version of the project.

//The next code break the run of the app
configuration.addAnnotatedClass( Author.class );
configuration.addAnnotatedClass( Book.class );

I run

./gradlew -Pagent run

./gradlew metadataCopy --task run --dir src/main/resources/META-INF/native-image

./gradlew nativeCompile

./app/build/native/nativeCompile/app

I always get a error. Any idea how fix this error?

1:46:09.234 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
11:46:09.234 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
11:46:09.234 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - Eating error closing SF on failed attempt to start it
**** Error ****
org.hibernate.MappingException: Could not instantiate persister org.hibernate.persister.entity.SingleTableEntityPersister
        at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:103)
        at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:75)
        at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.processBootEntities(MappingMetamodelImpl.java:278)
        at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.finishInitialization(MappingMetamodelImpl.java:211)
        at org.hibernate.metamodel.internal.RuntimeMetamodelsImpl.finishInitialization(RuntimeMetamodelsImpl.java:60)
        at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:311)
        at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:415)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:754)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:773)
        at test05.App.main(App.java:57)
Caused by: java.lang.IllegalArgumentException: Could not create type
        at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:170)
        at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:190)
        at org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState.load(ByteBuddyState.java:195)
        at org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState.loadProxy(ByteBuddyState.java:110)
        at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper.buildProxy(ByteBuddyProxyHelper.java:61)
        at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory.postInstantiate(ByteBuddyProxyFactory.java:65)
        at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.createProxyFactory(EntityRepresentationStrategyPojoStandard.java:273)
        at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.<init>(EntityRepresentationStrategyPojoStandard.java:155)
        at org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard.resolveStrategy(ManagedTypeRepresentationResolverStandard.java:60)
        at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:737)
        at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:152)
        at java.base@17.0.5/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        at java.base@17.0.5/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
        at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:92)
        ... 9 more
Caused by: java.lang.IllegalStateException: java.lang.UnsupportedOperationException: Defining new classes at runtime is not supported
        at net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup.injectRaw(ClassInjector.java:1640)
        at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:118)
        at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$UsingLookup.load(ClassLoadingStrategy.java:519)
        at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101)
        at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6317)
        at org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState$1.run(ByteBuddyState.java:203)
        at org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState$1.run(ByteBuddyState.java:199)
        at org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState.lambda$load$0(ByteBuddyState.java:212)
        at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:168)
        ... 22 more
Caused by: java.lang.UnsupportedOperationException: Defining new classes at runtime is not supported
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unimplemented(VMError.java:98)
        at java.base@17.0.5/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:46)
        at java.base@17.0.5/java.lang.reflect.Method.invoke(Method.java:568)
        at net.bytebuddy.utility.Invoker$Dispatcher.invoke(Unknown Source)
        at net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod.invoke(JavaDispatcher.java:1032)
        at net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler.invoke(JavaDispatcher.java:1162)
        at jdk.proxy4/jdk.proxy4.$Proxy62.defineClass(Unknown Source)
        at net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup.injectRaw(ClassInjector.java:1638)
        ... 30 more
Hello World!

My App.java look

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package test05;

import java.io.FileInputStream;
import java.util.Properties;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import domain.Author;
import domain.Book;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) {

        try {

            // Class.forName("com.mysql.cj.jdbc.Driver");

            // Connection con= DriverManager.getConnection( "jdbc:mysql://localhost:3306/Test01DB","root","dsystems01" );

            Properties properties = new Properties();

            try {
             
                System.out.println( "**** Reading... ****" );
                properties.load( new FileInputStream("/home/dsystems01/Desktop/Java/hibernate.cfg.properties") );

            }
            catch ( Exception e ) {
                
                e.printStackTrace();

            }

            System.out.println( "**** Configuration ****" );
            Configuration configuration = new Configuration().addProperties( properties );

            System.out.println( "**** Configuration Read ****" );


            //**** Begin the next code when is active break the native run of this app ****
            //**** but break in SessionFactory sessionFactory = configuration.buildSessionFactory();
            //**** Comment the next 2 lines to work not break anymore
            configuration.addAnnotatedClass( Author.class );
            configuration.addAnnotatedClass( Book.class );
            //**** End the previous code when is active break the native run of this app ****
            
            // //configuration.configure( new File( "/home/dsystems01/Desktop/Java/hibernate.cfg.properties" ) );
            // //configuration.addAnnotatedClass( Author.class );
            // //configuration.addAnnotatedClass( Book.class );

            // // Create Session Factory
            System.out.println( "**** SessionFactory ****" );
            //**** The next code break when .addAnnotatedClass code is active. in native run of this app ****
            SessionFactory sessionFactory = configuration.buildSessionFactory();

            // // Initialize Session Object

            System.out.println( "**** Open session ****" );
            Session session = sessionFactory.openSession();

            System.out.println( "**** Begin transaction ****" );
            var transaction = session.beginTransaction();
            
            // //

            System.out.println( "**** Commit ****" );
            transaction.commit();

            System.out.println( "**** Close ****" );
            sessionFactory.close();

        }
        catch ( Exception exception ) {

            System.out.println( "**** Error ****" );
            exception.printStackTrace();

        }


        System.out.println(new App().getGreeting());
        try {
           System.in.read();
        }
        catch ( Exception ex ) {

        }
    }
}

My build.gradle look

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/7.6/userguide/building_java_projects.html
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
    id 'org.graalvm.buildtools.native' version '0.9.19'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'

    // This dependency is used by the application.
    //implementation 'com.google.guava:guava:31.1-jre'

    //implementation 'org.slf4j:slf4j-api:2.0.5'
    implementation("org.hibernate:hibernate-core:6.1.6.Final")
    implementation("com.mysql:mysql-connector-j:8.0.31")
    implementation("org.hibernate.orm:hibernate-graalvm:6.1.6.Final")
    implementation 'org.hibernate.orm:hibernate-hikaricp:6.1.6.Final'
    implementation("org.hibernate.common:hibernate-commons-annotations:6.0.5.Final")

    implementation 'ch.qos.logback:logback-core:1.4.5'
    implementation 'ch.qos.logback:logback-classic:1.4.5'

    implementation("net.bytebuddy:byte-buddy:1.12.19")

    annotationProcessor 'org.hibernate.orm:hibernate-jpamodelgen:6.1.5.Final'

}

application {
    // Define the main class for the application.
    mainClass = 'test05.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

graalvmNative {

    agent {
      enableExperimentalPredefinedClasses = true
      enableExperimentalUnsafeAllocationTracing = true
    }

    metadataRepository {
      enabled = true
    }

    binaries.all {
      resources.autodetect()
    }
    
    binaries {
      main {

        buildArgs.add('--initialize-at-build-time=org.hibernate.internal.util.ReflectHelper')
        //buildArgs.add('--initialize-at-run-time=org.hibernate.cfg.Configuration')
        //buildArgs.add('--initialize-at-build-time=org.hibernate.internal,org.hibernate.jmx,org.hibernate.query')
        //buildArgs.add('--initialize-at-build-time=domain')

      }
    }

    toolchainDetection = false

}

The book class

package domain;

import java.time.Instant;

import jakarta.persistence.*;
//import javax.validation.constraints.Size;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="ID")
    private Long id;

    @Column(name="TITLE", unique = true)
    //@Size(max = 150)
    private String title;

    @Column(name="PUB_DATE")
    private Instant pubDate;

    @ManyToOne(targetEntity = Author.class)
    @JoinColumn(name = "AUTHOR")
    private Author author;

    public Book() {}

    public Book(String title, Instant pubDate, Author author) {
        this.title = title;
        this.pubDate = pubDate;
        this.author = author;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Instant getPubDate() {
        return pubDate;
    }

    public void setPubDate(Instant pubDate) {
        this.pubDate = pubDate;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }
}

The author class

package domain;

import jakarta.persistence.*;
//import javax.validation.constraints.Size;

@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="ID")
    private Long id;

    @Column(name="NAME", nullable = false, unique = true)
    //@Size(max = 100)
    private String name;

    @Column(name="BIRTH_YEAR", nullable = false)
    private Integer birthYear;

    public Author() {}

    public Author(String name, Integer birthYear) {
        this.name = name;
        this.birthYear = birthYear;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getBirthYear() {
        return birthYear;
    }

    public void setBirthYear(Integer birthYear) {
        this.birthYear = birthYear;
    }
}

You are on uncharted territory since not even Quarkus has been fully adapted yet to support native image compilation of Hibernate 6 apps AFAIK. Anyway, you at least need this hibernate-orm/hibernate-graalvm at main · hibernate/hibernate-orm · GitHub which will configure the native image tool for at least the static parts of Hibernate.

The dynamic configuration i.e. based on the entity classes is tough. I’d suggest you start with full reflection support for entity classes and try to gradually reduce the amount of reflection step by step.

Ok thanks!!!.

But. Any plan of Hibernate Core Team to support graalvm/native-image out the box? Maybe in version 7 or 8?

What do you mean by out of the box? The hibernate-graalvm artifact gets you pretty far already, but all the other reflection configuration is pretty much dynamic since it is based on your entity classes. native-image does not provide APIs to do this kind of thing AFAIU, which is also a reason for the existence of Quarkus.

With “out the box” I mean a standard project of maven/gradle. With no “help” or tons of dependencies in quarkus, micronaut, sprint boot, helidon. Java plain old school project, just add the dependence (hibernate-orm/mysql driver) in the pom.xml or build.gradle, and your are ready to go.

Just for you known EclipseLink/JPA work very well with GraalVM/native-image “out the box”. The example is:

Is because i choose EclipseLink over Hibernate, with GraalVM/native-image, for my “out the box” project.

Thanks in advance.

Dear Slt,
did you get it running in the meanwhile? I have exact the same situation. Need to compile hibernate with GraalVM to native executable (without any additional spring or quarkus). Do you have any updates? Really appreciate your response. Thank you.

Hello all,
I also created a reproducer, see: tonyre/HibernateSampleGraalVM (github.com)

1 Like

Hi, the same issue for me, Probably, new features of hibernate 6 try to initialize class at runtime, however, Static binaries from GraalVM does not support this feauter

No sorry. Hibernate never work. I tested eclipse link and work with native-image. But you need config. For the provider.xml.

I really tired of library and framework it use and abuse of runtime reflection. Is like heroine addictive but destructive.

Well, you chose the hard path. You could have just used Quarkus or other frameworks which do the heavy lifting for you, but if you don’t want to do that, then you will have to learn about what is necessary to get stuff done.
For a long time Java runtimes like the JVM just provided reflection and now comes along GraalVM native image which requires configuration for that to work, but at the same time doesn’t offer a flexible SPI for libraries to auto-configure this. So smart folks from Red Hat created Quarkus to do exactly that.
Maybe at some point Hibernate ORM will use some sort of annotation processing to generate accessors for your model, but until then Quarkus or lots of manual configuration is the best you can have if you want to use it in native image.

Sorry for that complication I prefer use rust or golang. Same complication but better support and performance boost.

I’m sure that when Rust is old enough to have popular libraries that are used for 20+ years like Hibernate ORM, then you will find quirks/inconveniences in those libraries that are due to limitations of the language/platform at that time.
The Java ecosystem was built on top of a dynamic runtime which can do speculative optimizations that outperform statically compiled languages in quite a few real world scenarios. This is because static compilers have to be conservative about things like available runtime types, probabilities of taken branches, target architecture instruction set etc. The conservative nature can either lead to the compiler producing huge binaries (covering all possible cases) or requiring configuration to reduce the dynamic possibilities (configure reflection). Native compilation has many knobs in general which you have to adjust to your needs if you want best performance and usually the tooling around debugging or telemetry in the native space is not very good compared to the Java ecosystem, but you’ll surely figure that out soon enough.

Would you mind explaining what is actually the technical difficulty to make hibernate native-ready? Is it just a matter of resources available? Thanks

1 Like

when we can expect hibernate latest version native ready?

Hibernate uses Java reflection extensively, which means that you will have to register your entity classes for reflection, so that GraalVM does preserve the metadata for that reflection information in the native image. There are likely other potential problems, but in the end, we decided to focus on providing a great native image experience in Quarkus, by doing some of the Hibernate ORM boot process earlier.
The Hibernate ORM team has no plans to support native image compilation outside of Quarkus. If anyone cares about this and wants to invest effort to make it happen, please start a discussion on our chat platform.

Well, everyone use reflection. Why your usage is so specific that it cannot be handled by compilers? Could you please elaborate on Hibernate for Quarkus? It’s still java, it’s still Hibernate, why the Quarkus is there as something that separates?

Well, everyone use reflection

That’s not true, but apart from that, most uses of reflection in other libraries are somewhat “static” i.e. reflection is only used to support optional dependencies.

Why your usage is so specific that it cannot be handled by compilers?

Like I wrote, you can just register your entity classes for reflection and maybe the GraalVM native image tool will just work then, though it’s probably not going to produce a small native image or startup very fast. Either way, registering entities for reflection is your task if you don’t want to use Quarkus, because Hibernate has no way to interact with the native image tool dynamically AFAIK, hence Hibernate can’t tell the native image tool to retain reflection information for your entities.

Could you please elaborate on Hibernate for Quarkus? It’s still java, it’s still Hibernate, why the Quarkus is there as something that separates?

Quarkus has build time tools with plugins that pre-process an application in a way that allows to serialize a partially booted SessionFactory into the native image. This will speed up the startup time of your application significantly and also allow for the native image tool to avoid including several other classes in the native image which are only used during boot time.