0
votes

TL;DR

Spring boot batch job does not get launched as an uber jar on windows or in Spring Cloud Data Flow; however, the same code works just fine when launched in Eclipse.


I created a simple Spring batch job using Spring Boot. I wanted to run this job on Kubernetes. I followed the steps mentioned in this guide on how to launch a Spring batch job in SCDF (Spring Cloud Data Flow). When I register this app and create a task as mentioned in the guide, a new pod is created in Kubernetes and the application runs fine; however, the batch job never gets launched. What could be the reason for this?

My Spring Boot batch application code :

Main Class

@SpringBootApplication
public class BatchApplication {

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

Job configuration

@Configuration
@EnableTask
@EnableBatchProcessing
@Profile("master")
public class BatchJob {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    public static final String JOB_NAME = "myJob";
    
    private static final Logger LOGGER = Logger.getLogger(BatchJob .class);

    @Bean(name = JOB_NAME )
    public Job job() {
        LOGGER.info("Creating job bean...");
        return jobBuilderFactory.get(JOB_NAME).incrementer(new RunIdIncrementer())
                .start(helloWorldStep()).build();
    }

    @Bean
    public Step helloWorldStep() {
        LOGGER.info("Creating Step bean...");
        return stepBuilderFactory.get("helloWorldStep").tasklet(helloWorldTasklet()).build();
    }

    @Bean
    public HelloWorldTasklet helloWorldTasklet() {
        LOGGER.info("Creating tasklet...");
        return new HelloWorldTasklet();
    }
}

HelloWorldTasklet

public class HelloWorldTasklet implements Tasklet {

    private static final Logger LOGGER = Logger.getLogger(HelloWorldTasklet.class);

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

        LOGGER.info("Hello World");
        System.out.println("Hello World Again");
        return RepeatStatus.FINISHED;

    }

}

application.properties

spring.application.name=my-batch-app
spring.datasource.url=jdbc:h2:mem:dataflow
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.cloud.task.closecontext_enabled=true

pom.xml dependencies

<?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>
        <artifactId>my-project-parent</artifactId>
        <groupId>com.myapp</groupId>
        <version>MAR21_1.0</version>
    </parent>

    <groupId>com.myapp</groupId>
    <artifactId>my-batch-app</artifactId>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
            <version>2.3.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.3.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.3.11.RELEASE</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
         <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17-cloudera1</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <!--<scope>runtime</scope>-->
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.0.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Where my-project-parent is the parent module for all my projects containing only defintions for jar versions. However, I am not really using any of these versions defined in this pom in my-batch-app as of now.

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.myapp</groupId>
    <artifactId>my-project-parent</artifactId>
    <version>MAR21_1.0</version>
    <packaging>pom</packaging>
    <name>my-project-parent</name>

    <properties>
        <java.version>1.8</java.version>            
        <spring.kafka.version>2.5.5.RELEASE</spring.kafka.version>          <spring.cloud.starter.task.version>2.2.4.RELEASE</spring.cloud.starter.task.version>            <spring.boot.starter.data.jpa.version>2.2.1.RELEASE</spring.boot.starter.data.jpa.version>
        <database.h2.version>1.4.199</database.h2.version>          <spring.cloud.dataflow.rest.client.version>2.7.1</spring.cloud.dataflow.rest.client.version>
        <jackson-datatype-jsr310.version>2.10.0</jackson-datatype-jsr310.version>
        <spring.batch.version>4.2.4.RELEASE</spring.batch.version>
        <log4jversion>1.2.17-cloudera1</log4jversion>
    </properties>

    <build>
        <plugins>
            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

I registered the docker image for the above application, created a task and launched the task from the SCDF UI. I don't see the Spring Batch step or job logs in my pod logs. Infact, I don't see the System.out.println or all the log statements statements that I have added all around the code in the logs.

The pod seems to be running BatchApplication class but does not seem to be launching the job at all. What am I missing? The same problem is observed even if I run the jar on windows as a spring boot uber jar

Picked up _JAVA_OPTIONS: -Djdk.tls.maxCertificateChainLength=20

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

2021-06-07 16:23:58.021  INFO 1 --- [           main] .r.c.b.w.b.c.o.BatchApplication : Starting BatchApplication  on batch-controller-test-5wg6eryzpj with PID 1 (/tmp/my-batch-app.jar started by ? in /tmp)
2021-06-07 16:23:58.026  INFO 1 --- [           main] .r.c.b.w.b.c.o.BatchApplication  : The following profiles are active: master
2021-06-07 16:24:03.031  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-06-07 16:24:03.316  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 185ms. Found 0 JPA repository interfaces.
2021-06-07 16:24:04.917  INFO 1 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2021-06-07 16:24:04.935  INFO 1 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2021-06-07 16:24:04.949  INFO 1 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2021-06-07 16:24:06.814  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.integration.config.IntegrationManagementConfiguration' of type [org.springframework.integration.config.IntegrationManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-06-07 16:24:07.047  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationChannelResolver' of type [org.springframework.integration.support.channel.BeanFactoryChannelResolver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-06-07 16:24:07.113  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-06-07 16:24:09.125  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-06-07 16:24:09.822  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-06-07 16:24:10.512  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-06-07 16:24:11.220  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.8.Final}
2021-06-07 16:24:12.724  INFO 1 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2021-06-07 16:24:14.019  INFO 1 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2021-06-07 16:24:16.335  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-06-07 16:24:16.429  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-06-07 16:24:19.814  INFO 1 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2021-06-07 16:24:21.721  INFO 1 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2021-06-07 16:24:21.721  INFO 1 --- [           main] o.s.i.channel.PublishSubscribeChannel    : Channel 'application.errorChannel' has 1 subscriber(s).
2021-06-07 16:24:21.722  INFO 1 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started bean '_org.springframework.integration.errorLogger'
2021-06-07 16:24:21.818  INFO 1 --- [           main] .r.c.b.w.b.c.o.BatchApplication  : Started BatchApplication  in 27.903 seconds (JVM running for 31.116)
2021-06-07 16:24:21.828  INFO 1 --- [extShutdownHook] o.s.i.endpoint.EventDrivenConsumer       : Removing {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2021-06-07 16:24:21.830  INFO 1 --- [extShutdownHook] o.s.i.channel.PublishSubscribeChannel    : Channel 'application.errorChannel' has 0 subscriber(s).
2021-06-07 16:24:21.830  INFO 1 --- [extShutdownHook] o.s.i.endpoint.EventDrivenConsumer       : stopped bean '_org.springframework.integration.errorLogger'
2021-06-07 16:24:21.832  INFO 1 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskScheduler          : Shutting down ExecutorService 'taskScheduler'
2021-06-07 16:24:21.916  INFO 1 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-06-07 16:24:21.917  INFO 1 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2021-06-07 16:24:21.920  INFO 1 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-06-07 16:24:22.014  INFO 1 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

JAR contents

Just to make things more clear, this is the list of folders that I see in the JAR. It looks like spring boot maven plugin was used to create the jar? Why then does the batch job not launch outside of Eclipse :

--my-batch-app.jar
 - BOOT-INF
   - classes
   - lib
 - META-INF
 - org

Note : As you can see, the logs also show that the master profile was indeed picked because I passed --spring.profiles.active=master as the input argument when launching the task.

2
I don't see any log from Spring Batch in what you shared. Before creating a docker image and deploying your app to kubernetes, can you run your app locally with java -jar myapp.jar and see if you batch job get launched? You need to make sure to correctly run your app locally before proceeding to docker and kubernetes.Mahmoud Ben Hassine
Yes I did that before posting the question. The same behavior is replicated on local as well as a stand alone job (Without @EnableTask). I am starting to think this has got to do witht he fact that my main class is not in a top level package or I need to add ComponentScan explicitly. Trying that now.Ping
Sorry for typos in my previous comment, I meant: "see if your batch job gets launched". ok, please test locally and update your question with the minimal code that reproduces the issue.Mahmoud Ben Hassine
The code above is complete. I tried copying the jar to another mount point and ran it without providing the classpath to the dependent jars. What this confirms is that spring boot starter batch is not being found on the classpath and is therefore not launching the Job automatically? I even tried explicitly providing the path to the dependencies folder where the starter batch jar is present java -cp dependency/* -jar my-batch-app.jar but this did not work.Ping
No, it is not complete, the pom does not show plugin definitions. Please check this: stackoverflow.com/help/minimal-reproducible-example. Use the spring boot maven plugin to create an uber-jar and run it with java -jar myapp.jar: docs.spring.io/spring-boot/docs/current/maven-plugin/reference/…Mahmoud Ben Hassine

2 Answers

0
votes

Few follow up questions-

Are you running this app as Scheduled or at Startup?

Do you have this starter dependency added?

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-batch</artifactId>
</dependency>

Based on your answers, I might need to dig into your code, if you feel comfortable sharing it.

0
votes

I tested your application and your problem is that your BatchJob class is not marked as a Spring configuration class. You should update it as follows:

// Your current imports
import org.springframework.context.annotation.Configuration;

@EnableTask
@EnableBatchProcessing
@Profile("master")
@Configuration // This is missing in your current config
public class BatchJob {
   ...
}

With that, I am able to see your log statements and the job is started correctly on application startup.