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);
}
}