# Hibernate ByteBuddy + GraalVM Native Image with Spring Boot (Full Workaround)

**URL:** <https://discourse.hibernate.org/t/hibernate-bytebuddy-graalvm-native-image-with-spring-boot-full-workaround/12186>\
**Category:** Hibernate ORM\
**Created:** [March 17, 2026, 5:38pm UTC](https://discourse.hibernate.org/t/hibernate-bytebuddy-graalvm-native-image-with-spring-boot-full-workaround/12186 "2026-03-17T17:38:00Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![Levi\_Houlton](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/levi_houlton/32/4293_2.png) [@Levi\_Houlton](https://discourse.hibernate.org/u/Levi_Houlton)\
**Post date:** [March 17, 2026, 5:38pm UTC](https://discourse.hibernate.org/t/hibernate-bytebuddy-graalvm-native-image-with-spring-boot-full-workaround/12186/1 "2026-03-17T17:38:00Z")

</div>

# Environment

- Spring Boot 3.3.11 (also tested with 3.5.9)

- Hibernate ORM 6.5.3.Final (also confirmed on 6.6.11.Final)

- GraalVM JDK 17.0.12 / 17.0.18

- Gradle 7.6

- Spring Cloud Stream with Kafka + Avro

- Hibernate Spatial with PostGIS

- PostgreSQL 17.5

- Docker (multi-stage build, Linux ARM64)

## The Problem

When compiling a Spring Boot microservice with Hibernate/JPA to a GraalVM native image, the application fails at runtime with:

```auto
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory':
org.hibernate.bytecode.spi.BytecodeProvider: org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl
Unable to get public no-arg constructor

```

Root cause: Hibernate’s `AggregatedServiceLoader` always loads the ByteBuddy `BytecodeProviderImpl` via classpath scanning, regardless of any configuration. ByteBuddy then tries to generate classes at runtime using `ClassLoader.defineClass()`, which GraalVM native image does not support (closed-world assumption).

## What Did NOT Work

We spent two full days trying every approach documented online. None of these resolved the issue:

1. **`hibernate.properties` with `bytecode.provider=none`** — Hibernate reads the property, but `AggregatedServiceLoader` runs independently and still loads ByteBuddy.

2. **`System.setProperty("hibernate.bytecode.provider", "none")` in `main()`** — Same issue. ServiceLoader runs before the property is evaluated.

3. **`META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider` override file** — Hibernate’s `AggregatedServiceLoader` scans ALL JARs on the classpath. It finds the ByteBuddy provider in `hibernate-core.jar` in addition to our override, then tries to instantiate both.

4. **Excluding `net.bytebuddy` from Gradle dependencies** — `hibernate-core.jar` bundles `BytecodeProviderImpl` internally. The class still references ByteBuddy classes, causing `ClassNotFoundException` on JVM or `NoSuchMethodException` in native image.

5. **GraalVM substitution class (`@TargetClass` / `@Substitute`)** — Method signature mismatch between our substitution and Hibernate 6.5.3’s internal `BytecodeProviderInitiator`.

6. **`hibernate-graalvm` dependency** — Did not resolve the ServiceLoader loading behavior.

7. **Hibernate enhance Gradle plugin alone** — Handles build-time bytecode enhancement but does not prevent ServiceLoader from loading ByteBuddy at runtime.

8. **`-H:ExcludeResources=META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider`** — Did not prevent Hibernate’s classpath scanning from finding the provider.

9. **Spring Boot AOT substitution** — Spring Framework has a built-in substitution in `SpringHibernateJpaPersistenceProvider` that sets `bytecode.provider=none` when `NativeDetector.inNativeImage()` returns true. This did not work in our setup, likely because our custom `native-image.gradle` configuration bypassed parts of Spring’s AOT processing pipeline.

10. **Upgrading Spring Boot to 3.5.9 / Hibernate to 6.6.11** — The same `AggregatedServiceLoader` behavior exists in all Hibernate 6.x and 7.x versions. The upgrade alone did not fix the issue.

## What Actually Worked

The solution is to **physically remove the ByteBuddy ServiceLoader registration from `hibernate-core.jar` before native image compilation**. This is done inside the Docker build.

This approach was inspired by @Vadym_Kazulkin’s post in this thread, where he successfully patched `hibernate-core.jar` and deployed to AWS Lambda.

### Step-by-Step Solution

#### Step 1: Update the Dockerfile

Add `zip` to the build dependencies and split the Gradle build into two phases: first compile (which downloads dependencies), then patch `hibernate-core.jar`, then native compile.

```auto
FROM --platform=linux/aarch64 ubuntu:22.04 AS graalvm-builder

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl wget python3 unzip zip \
        build-essential libz-dev ca-certificates git \
    && rm -rf /var/lib/apt/lists/*

RUN curl -L -o /tmp/graalvm.tar.gz \
    https://download.oracle.com/graalvm/17/archive/graalvm-jdk-17.0.12_linux-aarch64_bin.tar.gz \
    && mkdir -p /opt/graalvm \
    && tar -xzf /tmp/graalvm.tar.gz -C /opt/graalvm --strip-components=1 \
    && rm /tmp/graalvm.tar.gz

ENV GRAALVM_HOME=/opt/graalvm
ENV JAVA_HOME=$GRAALVM_HOME
ENV PATH=$GRAALVM_HOME/bin:$PATH

ARG SERVICE_MODULE
ARG SERVICE_NAME

WORKDIR /nup-dev
COPY ./ ./

# Phase 1: Compile to download dependencies
# Phase 2: Patch hibernate-core.jar to remove ByteBuddy ServiceLoader
# Phase 3: Native compile
RUN cd modules/${SERVICE_MODULE} && \
    ./gradlew clean compileJava --no-daemon && \
    find /root/.gradle/caches -name "hibernate-core-*.jar" -exec \
        zip -d {} "META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider" \; 2>/dev/null; \
    ./gradlew nativeCompile --no-daemon

```

#### Step 2: Add ServiceLoader Override File

Create `src/main/resources/META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider` containing:

```auto
org.hibernate.bytecode.internal.none.BytecodeProviderImpl

```

#### Step 3: Add System Property in Application Main

```auto
public static void main(String[] args) {
    System.setProperty("hibernate.bytecode.provider", "none");
    SpringApplication.run(Application.class, args);
}

```

#### Step 4: Add Reflect Config Entry

Add this to `src/main/resources/META-INF/native-image/reflect-config.json`:

```auto
{
    "name": "org.hibernate.bytecode.internal.none.BytecodeProviderImpl",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true
}

```

#### Step 5: Run the GraalVM Tracing Agent (per service)

Before building the native image, run the tracing agent on each service to generate complete reflection/proxy/resource metadata:

```auto
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
    -jar build/libs/your-service.jar

```

Let it run for a minute, then Ctrl+C. This generates the `reflect-config.json`, `proxy-config.json`, etc.

#### Step 6: Build and Test

```auto
docker build \
    --build-arg SERVICE_MODULE=your-module \
    --build-arg SERVICE_NAME=your-binary-name \
    -f Dockerfile.native \
    -t your-image:native .

```

## Why This Works

The `zip -d` command physically removes the `META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider` file from the cached `hibernate-core.jar` inside the Docker build container. With that file gone, Hibernate’s `AggregatedServiceLoader` can no longer find the ByteBuddy provider during classpath scanning. It falls back to the `none` provider registered by our override file.

The `System.setProperty` and ServiceLoader override file provide belt-and-suspenders redundancy. The reflect config entry ensures GraalVM includes the `none` provider class in the native image.

## Confirmed Working

We have successfully compiled and deployed 13 Spring Boot microservices as GraalVM native images using this approach, including services with:

- Hibernate Spatial + PostGIS

- Spring Cloud Stream + Kafka + Avro

- Spring Security OAuth2

- HikariCP connection pooling

All services start in under 1 second as native images versus 3+ seconds on JVM.

## Related Issues

- [HHH-19530](https://hibernate.atlassian.net/browse/HHH-19530) — Move ByteBuddy to separate artifact

- [HHH-17643](https://hibernate.atlassian.net/browse/HHH-17643) — Original discussion on disabling ByteBuddy

- This workaround is necessary until Hibernate provides a clean way to exclude the ByteBuddy `BytecodeProvider` from ServiceLoader scanning.

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [March 17, 2026, 5:41pm UTC](https://discourse.hibernate.org/t/hibernate-bytebuddy-graalvm-native-image-with-spring-boot-full-workaround/12186/2 "2026-03-17T17:41:18Z")

</div>

I’m glad you found a way to make it work in your environment, but just like I wrote in other topics about this matter already, you can use GraalVM mechanisms to substitute the `BytecodeProvider`. This works fine for Quarkus, so maybe your setup just does something weird. If Spring AOT is supposed to make this work already and you admit that your setup somehow bypasses that, then it’s just a problem in your build and you should probably spend time on fixing that instead of replacing files in the hibernate-core.jar.

---

<div class="post-metadata">

**Author:** ![Vadym\_Kazulkin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/vadym_kazulkin/32/4284_2.png) [@Vadym\_Kazulkin](https://discourse.hibernate.org/u/Vadym_Kazulkin)\
**Post date:** [March 17, 2026, 5:54pm UTC](https://discourse.hibernate.org/t/hibernate-bytebuddy-graalvm-native-image-with-spring-boot-full-workaround/12186/3 "2026-03-17T17:54:48Z")

</div>

I described [here](https://discourse.hibernate.org/t/how-to-correctly-disable-byte-buddy-for-graalvm-native-image/12163/6) how I could workaround the same problem. But I didn’t patch hiberate-core.jar, but built the uber-jar, provided no-op bytecode provider in my app (which then overrides the hibernate-core.jar ByteBuddy implementation). Alternatively you can filter out the ByteBuddy service loader in the META-INF of the uber-jar with maven plugin. And then I used the uber-jar to build GraalVM Native Image instead of the classpath. I also used this approach when also using Spring Boot (4) with JPA (and Hibernate) on AWS Lambda and have some examples in my [repo](https://github.com/Vadym79/aws-lambda-java-25-spring-boot-4) for it. The same works also without AWS Lambda.

But I’m glad that you also found the solution.

But I still think that the Hibernate’s `AggregatedServiceLoader` doesn’t work correctly or at least there is no clear way to explicitly configure the no-op implementation without doing dirty workarounds or magic that frameworks like Quarkus “somehow” do.
