Curtis Snowden
Curtis Snowden

Reputation: 493

Spring Boot Application Not Reading Application.properties in Tomcat

I'm trying to deploy a spring boot application as a WAR into Tomcat. When I have things hard-coded, it works just fine, and there are no problems. But after my initial testing, I tried to clean things up and go back to the properties. And I'm finding that my application.properties file is not being read by the application during deployment in Tomcat. And I'm just confused.

My initial testing with Tomcat, to get the JNDI configured, was hard-coded, and everything worked just fine then:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        return (DataSource) new JndiTemplate().lookup("java:/comp/env/jdbc/myJndiLocal");
    }
}

My context.xml is good and my tomcat's server.xml global resources are configured. The connection was made, and everything worked. Of course, hard-coded isn't what I want, so I switched to the properties.

Now, my problems are related to the org.springframework.boot.autoconfigure.jdbc.DataSourceProperties. I have my properties configured in Application.properties, which is in my resources directory:

spring.datasource.url=jdbc:postgresql://localhost:5432/MY_DB?currentSchema=public
spring.datasource.username=postgresql_username
spring.datasource.password=postgresql_password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.jndiName=jdbc/myJndiLocal

And I created a datasource configuration that prepares my database connection:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties) {

        System.out.printf(" !!!!! Datasource Config !!!!! %n" +
            "   url: %s%n" +
            "   username: %s%n" +
            "   password: %s%n" +
            "   driver: %s%n" +
            "   jndi: %s%n" +
            "   jndi full: %s%n",
            dataSourceProperties.getUrl(), dataSourceProperties.getUsername(), dataSourceProperties.getPassword(),
            dataSourceProperties.getDriverClassName(), dataSourceProperties.getJndiName(),
            String.format("java:/comp/env/%s", dataSourceProperties.getJndiName())
        );

        if (dataSourceProperties.getJndiName() != null && !dataSourceProperties.getUrl().isBlank()) {
            try {
                return (DataSource) new JndiTemplate().lookup(String.format("java:/comp/env/%s", dataSourceProperties.getJndiName()));
            }
            catch (Exception e) {
                System.out.println(" !!!!! JNDI Resource could not be found! Regular JDBC Configuration will be used instead. !!!!! ");
            }
        }

        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName(dataSourceProperties.getDriverClassName());
        dataSourceBuilder.url(dataSourceProperties.getUrl());
        dataSourceBuilder.username(dataSourceProperties.getUsername());
        dataSourceBuilder.password(dataSourceProperties.getPassword());
        return dataSourceBuilder.build();
    }
}

The first printf statement is just for current debugging, and I'll remove it later. My goal is to default to JNDI if present, otherwise use standard JDBC configuration. When running as a JAR with the embedded tomcat via my IDE, there are no problems. Properties are read into place, and everything gets configured as I expect.

 !!!!! Datasource Config !!!!! 
   url: jdbc:postgresql://localhost:5432/MY_DB?currentSchema=public
   username: postgresql_username
   password: postgresql_password
   driver: org.postgresql.Driver
   jndi: jdbc/myJndiLocal
   jndi full: java:/comp/env/jdbc/myJndiLocal
 !!!!! JNDI Resource could not be found! Regular JDBC Configuration will be used instead. !!!!! 

The problem now, however, is that my properties are all completely null when deployed as a WAR file into an external Tomcat.

 !!!!! Datasource Config !!!!!
   url: null
   username: null
   password: null
   driver: null
   jndi: null
   jndi full: java:/comp/env/null

I'm using the SpringBootServletInitializer for the WAR deployment, so I have to assume that is causing some kind of issue, but I'm at a loss presently as to what it is.

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EntityScan(basePackages = {"com.mycompany.myapp.model", "com.mycompany.model"})
@EnableJpaRepositories(basePackages = {"com.mycompany.dao.jpa"})
@ComponentScan(basePackages = {
    "com.mycompany.myapp",
    "com.mycompany.service",
    "com.mycompany.service.impl",
    "com.mycompany.myapp.webapp.pages",
    "com.mycompany.webapp.util",
    "com.mycompany.webapp.util.migration",
    "com.mycompany.myapp.annotations.validation",
    "com.mycompany.annotations"
})
public class MyAppApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyAppApplication.class);
    }
    
    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }
}

I'm on the latest Spring Boot, 3.3.4, and am using the latest Tomcat, 10.1.31. I'm also utilizing Maven for the dependencies and builds. I have already confirmed that the Application.properties is inside the proper location in the WAR file - /WEB-INF/classes/Application.properties. Why are my properties not being read into the application, and what can I do to address this? I've been doing some reading and found this question, which is similar, but I'm not doing anything inside of my main method. DataSourceProperties is a default Spring class, so I would have expected it would just populate. The use of it as a parameter to my configuration method I would have expected to force a jumpstart even if it wasn't prepared automatically.


Found a resolution, although I don't really understand why it is needed, nor why it's helping. I needed to add the @PropertySource annotation to my DataSourceConfig. Once I did that, all my properties were in place, and everything loaded up perfectly, connecting to my defined JNDI.

@Configuration
@PropertySource("classpath:Application.properties")
public class DataSourceConfig {
    ....
}

So, the Application.properties clearly must already have been on the classpath in order for that annotation to work properly. I'm now mainly confused as to why it wasn't being used by the application? Is there something about Tomcat deployment or my current configuration that is forcing Spring to utilize its own internal defaults and ignore my configuration files? The documentation is telling me that the file should be picked up automatically.

Upvotes: 0

Views: 255

Answers (2)

life888888
life888888

Reputation: 3144

  • Is your Application.properties a typo?

  • The default name should be lowercase application.properties.

In My First Answer, My Project.

If the file name of the configuration file is changed to ZZZApplication.properties ,

DemoJspApplication.java must be modified to:

ADD @PropertySource("classpath:ZZZApplication.properties") // Specifying a custom configuration file

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@PropertySource("classpath:ZZZApplication.properties") // Specifying a custom configuration file
@SpringBootApplication
public class DemoJspApplication {

    public static void main(String[] args) {
        System.out.println(">>> DemoJspApplication start");
        SpringApplication.run(DemoJspApplication.class, args);
        System.out.println(">>> DemoJspApplication after run");
    }

}

I've verified and tested it.

Upvotes: 1

life888888
life888888

Reputation: 3144

There's so much you can do, just using the default settings should be enough.

  • Spring Boot 3.3.4
  • Tomcat 10.1.31
  • JDK 17

Project Tree

demo-war-jsp-tag-db-Tomcat
├── pom.xml
├── Docker_MySQL
│   └── init.sql
├── Docker_PostgreSQL
│   └── init.sql
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── DemoJspApplication.java
        │           ├── ServletInitializer.java
        │           ├── HelloJDBCJSPController.java
        │           ├── HelloJSPController.java
        │           └── HelloController.java
        ├── resources
        │   └── application.properties
        └── webapp
            ├── index.jsp
            ├── index-tag-001.jsp
            ├── index-tag-002.jsp
            ├── index-tag-003.jsp
            └── WEB-INF
                └── jsp
                    ├── show-hello-jdbc.jsp
                    └── hello.jsp

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         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>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-jsp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>demo-jsp</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

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

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

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-websocket</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>jakarta.servlet.jsp.jstl</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>jakarta.servlet.jsp.jstl</groupId>
            <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
            <version>3.0.0</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>hello</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Docker_MySQL / init.sql

CREATE TABLE STUDENTS (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT,
    grade VARCHAR(10),
    email VARCHAR(100),
    enrollment_date DATE
);

INSERT INTO STUDENTS (name, age, grade, email, enrollment_date)
VALUES
('MySQL Alice', 20, 'A', '[email protected]', '2023-01-15'),
('MySQL Bob', 22, 'B', '[email protected]', '2023-02-10'),
('MySQL Charlie', 21, 'A', '[email protected]', '2023-03-12'),
('MySQL David', 23, 'C', '[email protected]', '2023-04-08'),
('MySQL Eva', 20, 'B', '[email protected]', '2023-05-05');

Docker_PostgreSQL / init.sql

CREATE TABLE IF NOT EXISTS STUDENTS (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT,
    grade VARCHAR(10),
    email VARCHAR(100),
    enrollment_date DATE
);

-- Insert sample data
INSERT INTO STUDENTS (name, age, grade, email, enrollment_date)
VALUES
('PostgreSQL Alice', 20, 'A', '[email protected]', '2023-01-15'),
('PostgreSQL Bob', 22, 'B', '[email protected]', '2023-02-10'),
('PostgreSQL Charlie', 21, 'A', '[email protected]', '2023-03-12'),
('PostgreSQL David', 23, 'C', '[email protected]', '2023-04-08'),
('PostgreSQL Eva', 20, 'B', '[email protected]', '2023-05-05');

src / main / java / com / example / DemoJspApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoJspApplication {

    public static void main(String[] args) {
        System.out.println(">>> DemoJspApplication start");
        SpringApplication.run(DemoJspApplication.class, args);
        System.out.println(">>> DemoJspApplication after run");
    }

}

src / main / java / com / example / ServletInitializer.java

package com.example;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

    public ServletInitializer() {
        System.out.println(">>> ServletInitializer() init");
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        System.out.println(">>> SpringApplicationBuilder configure()");
        return application.sources(DemoJspApplication.class);
    }

}

src / main / java / com / example / HelloJDBCJSPController.java

Just use @Autowired private DataSource dataSource;

package com.example;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.sql.DataSource;
import java.util.Map;

@Controller
public class HelloJDBCJSPController {

    @Autowired
    private DataSource dataSource;

    @RequestMapping(value = "/helloDS", method = RequestMethod.GET)
    public ModelAndView getDataSource(Map<String, Object> model) {
        System.out.println("HelloJDBCJSPController >>>> dataSource = " + dataSource);
        
        //Put the DataSource in the model
        model.put("dataSource", dataSource);
        
        // /WEB-INF/jsp/show-hello-jdbc.jsp
        return new ModelAndView("show-hello-jdbc");
    }

    @GetMapping("/loadDataSource")
    public String loadDataSource(HttpServletRequest request) {
        // Put the DataSource in the request
        request.setAttribute("dataSource", dataSource);

        // Forward to index-tag-003.jsp in the root directory
        return "forward:/index-tag-003.jsp";
    }

}

src / main / resources / application.properties

spring.application.name=hello-jsp
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# postgres
# spring.datasource.url=jdbc:postgresql://localhost:5432/demodb
# spring.datasource.username=demouser
# spring.datasource.password=Passw0rd!
# spring.datasource.driver-class-name=org.postgresql.Driver

# jndi datasource
spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource

src / main / webapp / WEB-INF / jsp / show-hello-jdbc.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="javax.sql.DataSource" %>

<%@ taglib uri="jakarta.tags.sql" prefix="sql" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Taglib 003 Example of sql tag</title>
</head>
<body>

<sql:query dataSource="${requestScope.dataSource}" var="result">
    SELECT * FROM STUDENTS;
</sql:query>

<table border="1">
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
    </tr>
    <c:forEach var="row" items="${result.rows}">
        <tr>
            <td><c:out value="${row.id}"/></td>
            <td><c:out value="${row.name}"/></td>
            <td><c:out value="${row.email}"/></td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

src / main / webapp / index-tag-003.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="javax.sql.DataSource" %>
<%@ taglib uri="jakarta.tags.sql" prefix="sql" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>

<%-- Check if requestScope.dataSource is null --%>
<c:if test="${requestScope.dataSource == null}">
    <jsp:forward page="/loadDataSource" />
</c:if>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Taglib 003 Example of sql tag</title>
</head>
<body>

<sql:query dataSource="${requestScope.dataSource}" var="result">
    SELECT * FROM STUDENTS;
</sql:query>

<table border="1">
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
    </tr>
    <c:forEach var="row" items="${result.rows}">
        <tr>
            <td><c:out value="${row.id}"/></td>
            <td><c:out value="${row.name}"/></td>
            <td><c:out value="${row.email}"/></td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

Please note that the content of this index-tag-003.jsp is very similar to show-hello-jdbc.jsp, except for a small section of content.

It contains the following paragraph:

<c:if test="${requestScope.dataSource == null}">
    <jsp:forward page="/loadDataSource" />
</c:if>

/loadDataSource => HelloJDBCJSPController.java

@Autowired
private DataSource dataSource; // read it from application.properties
    
public String loadDataSource(HttpServletRequest request) {
        // Put the DataSource in the request
        request.setAttribute("dataSource", dataSource);
        ....

Build

mvn clean package

Install and Config

Insatall war to Tomcat

copy target/hello.war

to Tomcat/webapps/

Config Tomcat JNDI Datasource

cd Tomcat/conf/
mkdir -p Catalina/localhost

create hello.xml

hello.xml

<!-- hello Context -->
<Context docBase="${catalina.base}/webapps/examples" reloadable="true">
         
    <!-- JNDI resource configuration -->

    <!-- JNDI DataSource Configuration for PostgreSQL -->
    <!-- spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource -->
    <Resource
        name="jdbc/demoPostgresDataSource"
        auth="Container"
        type="javax.sql.DataSource"
        maxTotal="100"
        maxIdle="30"
        maxWaitMillis="10000"
        username="demouser"
        password="Passw0rd!"
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://localhost:5432/demodb"
        validationQuery="SELECT 1"/>

    <!-- JNDI DataSource Configuration for MySQL -->
    <!-- spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource -->
    <Resource
        name="jdbc/demoMysqlDataSource"
        auth="Container"
        type="javax.sql.DataSource"
        maxTotal="100"
        maxIdle="30"
        maxWaitMillis="10000"
        username="demouser"
        password="Passw0rd!"
        driverClassName="com.mysql.cj.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/demodb"
        validationQuery="SELECT 1"/>

</Context>

Download Jar

org.postgresql:postgresql:jar:42.7.4

Copy Jar to Tomcat/lib

  • postgresql-42.7.4.jar
  • checker-qual-3.42.0.jar
  • mysql-connector-j-8.3.0.jar
  • protobuf-java-3.25.1.jar

Prepare Database and Start Database

PostgreSQL

cd Docker_PostgreSQL

docker run \
  --name my-postgres \
  -e POSTGRES_USER=demouser \
  -e POSTGRES_PASSWORD=Passw0rd! \
  -e POSTGRES_DB=demodb \
  -v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
  -p 5432:5432 \
  -d \
  postgres

MySQL

cd Docker_MySQL

docker run \
    --name my-mysql \
    -e MYSQL_ROOT_PASSWORD=Passw0rd! \
    -e MYSQL_DATABASE=demodb \
    -e MYSQL_USER=demouser \
    -e MYSQL_PASSWORD=Passw0rd! \
    -v `pwd`/init.sql:/docker-entrypoint-initdb.d/init.sql \
    -p 3306:3306 \
    -d \
    mysql

Test

Test 1 use default - jndi demoPostgresDataSource

start tomcat

cd Tomcat/bin
./startup.sh

Test URL

  • http://localhost:8080/hello/index-tag-003.jsp

001.png

  • http://localhost:8080/hello/helloDS

002.png

Note that it now displays the data content of PostgreSQL Database data.

check Tomcat/webapps/hello/WEB-INF/classes/application.properties

It use spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource

application.properties

# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# jndi datasource
spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource

It should be noted that when spring.datasource.jndi-name is set, Spring Boot will use the jndi setting first, and then ignore spring.datasource.url, spring.datasource.username, spring.datasource.password, spring.datasource.driver-class-name settings.

Stop Tomcat now.

cd Tomcat/bin
./shutdown.sh

Test 2 use - spring.datasource.xxxx config

  • Delete hello.war
cd Tomcat/webapps
rm hello.war
  • Keep the Tomcat/webapps/hello directory.

  • Modify the contents of the Tomcat/webapps/hello/WEB-INF/classes/application.properties file:

Set

spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource

to comment and change it to

# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource

Tomcat/webapps/hello/WEB-INF/classes/application.properties

spring.application.name=hello-jsp
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# postgres
# spring.datasource.url=jdbc:postgresql://localhost:5432/demodb
# spring.datasource.username=demouser
# spring.datasource.password=Passw0rd!
# spring.datasource.driver-class-name=org.postgresql.Driver

# jndi datasource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource

start tomcat

cd Tomcat/bin
./startup.sh

Test URL

  • http://localhost:8080/hello/index-tag-003.jsp

003.png

  • http://localhost:8080/hello/helloDS

004.png

Note that it now displays the data content of MySQL Database data.

Note that spring.datasource.jndi-name (Postgres) is now used instead of spring.datasource.url (MySQL).

Tomcat/webapps/hello/WEB-INF/classes/application.properties

application.properties

# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource

Upvotes: 1

Related Questions