0

I am currently experimenting with the GraalVM native-image tool. The goal is to run a spring boot webapp in postgresql.

My project compiles with mvn clean package and the .JAR runs like a charm with docker-postgresql, yet I am unable to build a native-image out of this.

I found this neat compile script from Josh Long from Spring tips which helped a lot with the configuration in previous projects. So far, I have no clue why the native-image wont build since its just throwing a StringIndexOutOfBoundsException. I have another project with h2 and jpa where it does just that with exactly the same parameters.

Hoping for some suggestions where to change the config or experiences and ofc solutions.

I am currently using GraalVM Version 20.1.0 (Java Version 1.8.0_252) as JDK.

Below is the stacktrace and the sample project. I also uploaded the terminal output of ./compile.sh here https://file.io/HXudRpH80dDz because its too long if that helps.

Fatal error:java.lang.StringIndexOutOfBoundsException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
    at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1005)
    at com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:463)
    at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:359)
    at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:518)
    at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:117)
Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: -1
    at java.lang.String.substring(String.java:1967)
    at org.springframework.graalvm.type.TypeSystem.resolve(TypeSystem.java:208)
    at org.springframework.graalvm.type.Method.getReturnType(Method.java:199)
    at org.springframework.graalvm.type.Type.collectAtMappingMarkedReturnTypes(Type.java:1655)
    at org.springframework.graalvm.support.ResourcesHandler.processResponseBodyComponent(ResourcesHandler.java:428)
    at org.springframework.graalvm.support.ResourcesHandler.processSpringComponents(ResourcesHandler.java:310)
    at org.springframework.graalvm.support.ResourcesHandler.processExistingOrSynthesizedSpringComponentsFiles(ResourcesHandler.java:177)
    at org.springframework.graalvm.support.ResourcesHandler.register(ResourcesHandler.java:124)
    at org.springframework.graalvm.support.SpringFeature.beforeAnalysis(SpringFeature.java:78)
    at com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$7(NativeImageGenerator.java:679)
    at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:70)
    at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:679)
    at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:538)
    at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:451)
    at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Error: Image build request failed with exit status 1
com.oracle.svm.driver.NativeImage$NativeImageError: Image build request failed with exit status 1
    at com.oracle.svm.driver.NativeImage.showError(NativeImage.java:1541)
    at com.oracle.svm.driver.NativeImage.build(NativeImage.java:1299)
    at com.oracle.svm.driver.NativeImage.performBuild(NativeImage.java:1260)
    at com.oracle.svm.driver.NativeImage.main(NativeImage.java:1219)

real    0m36,107s
user    1m0,314s
sys 0m2,100s

compile.sh

#!/usr/bin/env bash

ARTIFACT=${1}
MAINCLASS=${2}
VERSION=${3}

JAR="${ARTIFACT}-${VERSION}.jar"

rm -rf target
mkdir -p target/native-image
mvn package  
rm -f $ARTIFACT
cd target/native-image
jar -xvf ../$JAR  
cp -R META-INF BOOT-INF/classes

LIBPATH=`find BOOT-INF/lib | tr '\n' ':'`
CP=BOOT-INF/classes:$LIBPATH
GRAALVM_VERSION=`native-image --version`

time native-image \
  --verbose \
  -H:EnableURLProtocols=http \
  -H:+RemoveSaturatedTypeFlows \
  -H:Name=$ARTIFACT \
  -H:+TraceClassInitialization \
  --initialize-at-build-time=org.springframework.util.unit.DataSize \
  -H:+ReportExceptionStackTraces  \
  -Dspring.native.verbose=true \
  -Dspring.native.remove-jmx-support=true \
  -Dspring.native.remove-spel-support=true \
  -Dspring.native.remove-yaml-support=true \
  -cp $CP $MAINCLASS  \
  --no-fallback\
  --allow-incomplete-classpath\
  -Dspring.native.remove-xml-support=true 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>postgres</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for postgresql native-image</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!--  -->
        <dependency>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-graalvm-native</artifactId>
            <version>0.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-indexer</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>

            <!--
            <plugin>
                <groupId>org.hibernate.orm.tooling</groupId>
                <artifactId>hibernate-enhance-maven-plugin</artifactId>
                <version>${hibernate.version}</version>
                <executions>
                    <execution>
                        <configuration>
                            <failOnError>true</failOnError>
                            <enableLazyInitialization>true</enableLazyInitialization>
                            <enableDirtyTracking>true</enableDirtyTracking>
                            <enableExtendedEnhancement>false</enableExtendedEnhancement>
                        </configuration>
                        <goals>
                            <goal>enhance</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

application.properties

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.show-sql=true
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=admin


spring.datasource.initialization-mode=always
spring.datasource.schema=classpath:/schema.sql
spring.datasource.continue-on-error=true

PostgresApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;

@SpringBootApplication(
        exclude = SpringDataWebAutoConfiguration.class,
        proxyBeanMethods = false
)
public class PostgresApplication {

    public static void main(String[] args) {
        SpringApplication.run(PostgresApplication.class, args);
    }

}

Employee.java

public class Employee {

String employeeId;
String employeeName;
String employeeEmail;
String employeeAddress;


public String getEmployeeEmail() {
    return employeeEmail;
}
public void setEmployeeEmail(String employeeEmail) {
    this.employeeEmail = employeeEmail;
}
public String getEmployeeId() {
    return employeeId;
}
public void setEmployeeId(String employeeId) {
    this.employeeId = employeeId;
}
public String getEmployeeName() {
    return employeeName;
}
public void setEmployeeName(String employeeName) {
    this.employeeName = employeeName;
}
public String getEmployeeAddress() {
    return employeeAddress;
}
public void setEmployeeAddress(String employeeAddress) {
    this.employeeAddress = employeeAddress;
}}

EmployeeRowMapper.java

import java.sql.ResultSet;
import java.sql.SQLException;

import com.example.postgres.entity.Employee;
import org.springframework.jdbc.core.RowMapper;

public class EmployeeRowMapper implements RowMapper<Employee> {

    @Override
    public Employee mapRow(ResultSet rs, int arg1) throws SQLException {
        Employee emp = new Employee();
        emp.setEmployeeId(rs.getString("employeeId"));
        emp.setEmployeeName(rs.getString("employeeName"));
        emp.setEmployeeEmail(rs.getString("employeeEmail"));
        emp.setEmployeeAddress(rs.getString("employeeAddress"));

        return emp;
    }
}

EmployeeDao.java

import com.example.postgres.entity.Employee;

import java.util.List;

public interface EmployeeDao {

    List<Employee> findAll();

    void insertEmployee(Employee emp);

    void updateEmployee(Employee emp);

    void executeUpdateEmployee(Employee emp);

    public void deleteEmployee(Employee emp);
}

EmployeeDaoImpl.java

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.example.postgres.entity.Employee;
import com.example.postgres.mapper.EmployeeRowMapper;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeDaoImpl implements EmployeeDao{
    
    public EmployeeDaoImpl(NamedParameterJdbcTemplate template) {  
        this.template = template;  
}  
    NamedParameterJdbcTemplate template;  

    @Override
    public List<Employee> findAll() {
        return template.query("select * from employee", new EmployeeRowMapper());
    }
    @Override
    public void insertEmployee(Employee emp) {
         final String sql = "insert into employee(employeeId, employeeName , employeeAddress,employeeEmail) values(:employeeId,:employeeName,:employeeEmail,:employeeAddress)";
         
            KeyHolder holder = new GeneratedKeyHolder();
            SqlParameterSource param = new MapSqlParameterSource()
                    .addValue("employeeId", emp.getEmployeeId())
                    .addValue("employeeName", emp.getEmployeeName())
                    .addValue("employeeEmail", emp.getEmployeeEmail())
                    .addValue("employeeAddress", emp.getEmployeeAddress());
            template.update(sql,param, holder);
     
    }
    
    @Override
    public void updateEmployee(Employee emp) {
         final String sql = "update employee set employeeName=:employeeName, employeeAddress=:employeeAddress, employeeEmail=:employeeEmail where employeeId=:employeeId";
         
            KeyHolder holder = new GeneratedKeyHolder();
            SqlParameterSource param = new MapSqlParameterSource()
                    .addValue("employeeId", emp.getEmployeeId())
                    .addValue("employeeName", emp.getEmployeeName())
                    .addValue("employeeEmail", emp.getEmployeeEmail())
                    .addValue("employeeAddress", emp.getEmployeeAddress());
            template.update(sql,param, holder);
     
    }
    
    @Override
    public void executeUpdateEmployee(Employee emp) {
         final String sql = "update employee set employeeName=:employeeName, employeeAddress=:employeeAddress, employeeEmail=:employeeEmail where employeeId=:employeeId";
             

         Map<String,Object> map=new HashMap<String,Object>();  
         map.put("employeeId", emp.getEmployeeId());
         map.put("employeeName", emp.getEmployeeName());
         map.put("employeeEmail", emp.getEmployeeEmail());
         map.put("employeeAddress", emp.getEmployeeAddress());
    
         template.execute(sql,map,new PreparedStatementCallback<Object>() {  
                @Override  
                public Object doInPreparedStatement(PreparedStatement ps)  
                        throws SQLException, DataAccessException {  
                    return ps.executeUpdate();  
                }  
            });  

     
    }
    
    @Override
    public void deleteEmployee(Employee emp) {
         final String sql = "delete from employee where employeeId=:employeeId";
             

         Map<String,Object> map=new HashMap<String,Object>();  
         map.put("employeeId", emp.getEmployeeId());
    
         template.execute(sql,map,new PreparedStatementCallback<Object>() {  
                @Override  
                public Object doInPreparedStatement(PreparedStatement ps)  
                        throws SQLException, DataAccessException {  
                    return ps.executeUpdate();  
                }  
            });  
    }
}

EmployeeService.java

import java.util.List;

import com.example.postgres.entity.Employee;

public interface EmployeeService {
    List<Employee> findAll();

    void insertEmployee(Employee emp);

    void updateEmployee(Employee emp);

    void executeUpdateEmployee(Employee emp);

    void deleteEmployee(Employee emp);
    
}

EmployeeServiceImpl.java

import javax.annotation.Resource;
import java.util.List;

import com.example.postgres.dao.EmployeeDao;
import com.example.postgres.entity.Employee;
import org.springframework.stereotype.Component;

@Component
public class EmployeeServiceImpl implements EmployeeService{
    @Resource
    EmployeeDao employeeDao;
    @Override
    public List<Employee> findAll() {
        return employeeDao.findAll();
    }
    @Override
    public void insertEmployee(Employee emp) {
        employeeDao.insertEmployee(emp);
        
    }
    @Override
    public void updateEmployee(Employee emp) {
        employeeDao.updateEmployee(emp);
        
    }
    @Override
    public void executeUpdateEmployee(Employee emp) {
        employeeDao.executeUpdateEmployee(emp);
        
    }

    @Override
    public void deleteEmployee(Employee emp) {
        employeeDao.deleteEmployee(emp);
        
    }
}

ApplicationController.java

import java.util.List;

import javax.annotation.Resource;

import com.example.postgres.entity.Employee;
import com.example.postgres.service.EmployeeService;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/postgresApp")
public class ApplicationController {

    @Resource
    EmployeeService employeeService;
    
    @GetMapping(value = "/employeeList")
    public List<Employee> getEmployees() {
        return employeeService.findAll();
    
    }
    
    @PostMapping(value = "/createEmp")
    public void createEmployee(@RequestBody Employee emp) {
         employeeService.insertEmployee(emp);
    
    }
    @PutMapping(value = "/updateEmp")
    public void updateEmployee(@RequestBody Employee emp) {
         employeeService.updateEmployee(emp);
    
    }
    @PutMapping(value = "/executeUpdateEmp")
    public void executeUpdateEmployee(@RequestBody Employee emp) {
         employeeService.executeUpdateEmployee(emp);
    
    }
    
    @DeleteMapping(value = "/deleteEmpById")
    public void deleteEmployee(@RequestBody Employee emp) {
         employeeService.deleteEmployee(emp);
    
    }
    
}
  • It's most probably a bug, can you please create an issue at github.com/oracle/graal with the reproducer? – Oleg Šelajev Aug 17 '20 at 12:11
  • I opened an issue on github https://github.com/spring-projects-experimental/spring-graalvm-native/issues/234 I still think there has to be a way to make a PostgreSQL database work. I was having good experiences with the native-image until i tried to achieve this. – DrNaughtyDog Aug 18 '20 at 06:38

0 Answers0