spring-projects / spring-projects/spring-batch

Unable to correctly save task metadata when registering JobExecutionShutdownHook

Open
#5,307 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-reporter type: bug
Dominant language
Java
Stars
3k
Forks
2.5k
Avg merge
6d 53m
Merged PRs (30d)
3

Description

Bug description
By registering a JVM exit callback using JobExecutionShutdownHook, and then using kill to shut down the JVM program during task execution, you will see an exception indicating that the database operation failed.

Environment
openjdk 25.0.2 2026-01-20 LTS
spring batch 6.0.2
spring boot 4.0.3

Steps to reproduce
registering a JVM exit callback, and execute command kill 62989

    val hook = JobExecutionShutdownHook(jobExecution, jobOperator)
    Runtime.getRuntime().addShutdownHook(hook)

I also tried SpringApplication.getShutdownHandlers().add(hook), but it still couldn't save the work to the DB.

The exception log shows that this line threw a database operation exception:

org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)

full log:

2026-03-01T15:19:45.071+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.JobExecutionShutdownHook     : Received JVM shutdown signal
2026-03-01T15:19:45.071+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.JobExecutionShutdownHook     : Attempting to gracefully stop job execution 228
2026-03-01T15:19:45.072+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.TaskExecutorJobOperator      : Stopping job execution: JobExecution: id=228, version=1, startTime=2026-03-01T15:19:09.782276, endTime=null, lastUpdated=2026-03-01T15:19:09.782424, status=STARTED, exitStatus=exitCode=UNKNOWN;exitDescription=, job=[JobInstance: id=225, version=0, Job=[contractAnalysisJob]], jobParameters=[{JobParameter{name='task', value=ContractScanTask(analyzer=Semgrep, analysisId=1190, filePath=null, objKey=adds, extra=null, contractAddress=x faf88wf aaw a]odwdded 9aw d, chainType=ETH, dataType=SRC), type=class io.github.xhstormr.sast.model.contract.ContractScanTask, identifying=true}}]
2026-03-01T15:19:45.578+08:00  INFO 34906 --- [sast-web3-consumer] [ionShutdownHook] i.g.x.s.d.c.handler.ApplicationLogger    : Application is destroyed!
2026-03-01T15:19:45.589+08:00  WARN 34906 --- [sast-web3-consumer] [      Thread-12] com.zaxxer.hikari.pool.ProxyConnection   : HikariPool-1 - Connection org.postgresql.jdbc.PgConnection@665f7826 marked as broken because of SQLSTATE(08006), ErrorCode(0)

org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:456)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:387)
	at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:998)
	at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:1020)
	at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:378)
	at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
	at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:84)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:242)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:553)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:687)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:408)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy248.update(Unknown Source)
	at org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)
	at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.stop(TaskExecutorJobOperator.java:139)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:133)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy266.stop(Unknown Source)
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:55)
Caused by: java.net.SocketException: Socket closed
	at java.base/sun.nio.ch.NioSocketImpl.endRead(NioSocketImpl.java:242)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:321)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:354)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:798)
	at java.base/java.net.Socket$SocketInputStream.implRead(Socket.java:974)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:964)
	at org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:192)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:159)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:144)
	at org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:76)
	at org.postgresql.core.PGStream.receiveChar(PGStream.java:477)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2314)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:429)
	... 32 common frames omitted

Exception in thread "Thread-12" java.lang.RuntimeException: Unable to gracefully stop job execution 228
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:62)
Caused by: org.springframework.dao.DataAccessResourceFailureException: Hibernate transaction: Unable to commit against JDBC Connection; An I/O error occurred while sending to the backend.
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:152)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:102)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:111)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:144)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:131)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.translateExceptionIfPossible(HibernateExceptionTranslator.java:105)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:557)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:687)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:408)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy248.update(Unknown Source)
	at org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)
	at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.stop(TaskExecutorJobOperator.java:139)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:133)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy266.stop(Unknown Source)
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:55)
Caused by: org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:456)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:387)
	at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:998)
	at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:1020)
	at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:378)
	at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
	at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:84)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:242)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:553)
	... 22 more
Caused by: java.net.SocketException: Socket closed
	at java.base/sun.nio.ch.NioSocketImpl.endRead(NioSocketImpl.java:242)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:321)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:354)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:798)
	at java.base/java.net.Socket$SocketInputStream.implRead(Socket.java:974)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:964)
	at org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:192)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:159)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:144)
	at org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:76)
	at org.postgresql.core.PGStream.receiveChar(PGStream.java:477)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2314)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:429)
	... 32 more


Expected behavior
Able to save task metadata and gracefully exit, no exception occurs.

Minimal Complete Reproducible example
Please provide a failing test or a minimal complete verifiable example that reproduces the issue.
Bug reports that are reproducible will take priority in resolution over reports that are not reproducible.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at JobExecutionShutdownHook.run (JobExecutionShutdownHook.java:55) and follow the stop call through TaskExecutorJobOperator to SimpleJobOperator.stop (SimpleJobOperator.java:349). Reproduce the failure with a JVM shutdown triggered by kill and inspect whether the PostgreSQL connection remains available during shutdown. Done means the job metadata is saved without the reported database exception.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, postgresql, spring
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.