javajarh2

H2 Database connects in IDE, but not from console or as a JAR file


I'm trying to make a Maven project that connects to an H2 database. In an IDE this works fine, but when executing the compiled project in the terminal, or when building the project into a JAR file and executing it, I get the following error:

No suitable driver found for jdbc:h2:./diary;INIT=RUNSCRIPT FROM 'classpath:diaries.sql';
    at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:638)
    at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:222)
    at edu.ntnu.idi.idat.TestCode.main(TestCode.java:18)

Here's the command I used to run the project jar file:

java -jar target/project-1.0-SNAPSHOT.jar

Here's the command I used to run the project directly:

java -cp target/classes/ edu.ntnu.idi.idat.TestCode

What does it mean to not find suitable drivers, and how can I ensure a successful database connection?

I've tried moving the JAR file around my project folder, from the target folder, to the root of the project folder, and even to the folder where I have my TestCode.java file.

A post with a similar error mentions class loaders. I'm not sure what that is, and I haven't done anything related to that, so I don't believe that's relevant to my problem, although I might be wrong.

Code:

package edu.ntnu.idi.idat;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public final class TestCode {
    private TestCode() { }
    /**
     * Test code.
     * @param args
     */
    public static void main(
        final String[] args
    ) {
        try {
            Connection connection = DriverManager.getConnection(
                "jdbc:h2:./diary;INIT=RUNSCRIPT FROM 'classpath:diaries.sql';"
            );
            System.out.println(
                "connection.isValid() = "
                + connection.isValid(0)
            );
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Here's the pom.xml file as requested:

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

  <groupId>edu.ntnu</groupId>
  <artifactId>project</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>project</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>2.4.240</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>7.0.0-M9</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.5.0</version>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <mainClass>edu.ntnu.idi.idat.TestCode</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.1</version>
          <configuration>
            <source>11</source>
            <target>11</target>
            <compilerArgs>
              <arg>--add-exports=java.base/com.sun.crypto.provider=ALL-UNNAMED</arg>
            </compilerArgs>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

diaries.sql:

create table if not exists DIARIES (id identity primary key, title varchar (255) not null, author varchar (255) not null, dating TIMESTAMP not null, diarytype varchar (255) not null, public BOOLEAN not null, password varchar(255), contents MEDIUMBLOB (100000) not null);

Possibly relevant parts of the file structure:

relevant parts of a project file structure

Edit:

I believe it's different from the question how IntelliJ is able to resolve dependencies and run a spring boot (maven) application while from the command line, it is not possible?, as I'm already using the maven-jar-plugin to build dependencies into my jar file.


Solution

  • The error you are seeing is because the JAR you are running is not bundled with the dependencies, hence when you run it, it can not find the H2 driver which comes from the dependency in your POM. In the IDE it works because it resolves the classpath automatically when you run your project. The equivalent in the terminal would be something like: java -jar yourjarhere -cp path_for_dependencies (note this is from memory and its been light years I don't use this :D )

    You should also read and understand java classpath: https://www.baeldung.com/java-classpath-vs-build-path

    OK, lets start with a clean up.

    Change your pom file such that it only includes the necessary dependencies and plugins in order to build a "fat jar" that you can run from your terminal with java -jar...

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>edu.ntnu</groupId>
      <artifactId>project</artifactId>
      <version>1.0-SNAPSHOT</version>
      <name>project</name>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
      </properties>
    
      <dependencies>
        <!-- H2 database 
         so far this is the only dependency you need in your project
         -->
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
          <version>2.4.240</version>
        </dependency>
    
      </dependencies>
    
      <build>
        <plugins>
          <!-- Java compiler -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
              <source>${maven.compiler.source}</source>
              <target>${maven.compiler.target}</target>
            </configuration>
          </plugin>
    
          <!-- Fat JAR with main class 
          this is a plugin that will add all the dependencies within your jar
          such that you dont need to specify the classpath when running your class
          -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.5.3</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>shade</goal>
                </goals>
                <configuration>
                  <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                      <mainClass>edu.ntnu.idi.idat.TestCode</mainClass>
                    </transformer>
                  </transformers>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
    

    Once that is done you can use in the terminal:

    mvn clean package
    java -jar target/project-1.0-SNAPSHOT-shaded.jar
    

    The mvn command is to generate your jar, and java ... to execute it. Give it a try and let me know.