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.