Connecting to Heroku Postgres from Spring Boot Connecting to Heroku Postgres from Spring Boot spring spring

Connecting to Heroku Postgres from Spring Boot


Simplest cleanest way for Spring Boot 2.x with Heroku & Postgres

I read all answers, but didn´t find what Jonik was looking for:

I'm looking for the simplest, cleanest way of connecting to Heroku Postgres in a Spring Boot app using JPA/Hibernate

The development process most people want to use with Spring Boot & Heroku includes a local H2 in-memory database for testing & fast development cycles - and the Heroku Postgres database for staging and production on Heroku.

  • First thing is - you don´t need to use Spring profiles for that!
  • Second: You don´t need to write/change any code!

Let´s have a look on what we have to do step by step. I have a example project in place that provides a fully working Heroku deployment and configuration for Postgres - only for the sake of completeness, if you want to test it yourself: github.com/jonashackt/spring-boot-vuejs.

The pom.xml

We need the following depencencies:

    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-jpa</artifactId>    </dependency>    <!-- In-Memory database used for local development & testing -->    <dependency>        <groupId>com.h2database</groupId>        <artifactId>h2</artifactId>    </dependency>    <!-- Switch back from Spring Boot 2.x standard HikariCP to Tomcat JDBC,    configured later in Heroku (see https://stackoverflow.com/a/49970142/4964553) -->    <dependency>        <groupId>org.apache.tomcat</groupId>        <artifactId>tomcat-jdbc</artifactId>    </dependency>    <!-- PostgreSQL used in Staging and Production environment, e.g. on Heroku -->    <dependency>        <groupId>org.postgresql</groupId>        <artifactId>postgresql</artifactId>        <version>42.2.2</version>    </dependency>

One tricky thing here is the usage of tomcat-jdbc, but we´ll cover that in a second.

Configure Environment Variables on Heroku

In Heroku Environment Variables are named Config Vars. You heard right, all we have to do is to configure Enviroment Variables! We just need the correct ones. Therefore head over to https://data.heroku.com/ (I assume there´s already a Postgres database configured for your Heroku app, which is the default behavior).

Now click on your application´s corresponding Datastore and switch over to the Settings tab. Then click on View Credentials..., which should look something similar like this:

heroku-datastore-credentials-postgres

Now open a new browser tab and go to your Heroku application´s Settings tab also. Click on Reveal Config Vars and create the following Environment Variables:

  • SPRING_DATASOURCE_URL = jdbc:postgresql://YourPostgresHerokuHostNameHere:5432/YourPostgresHerokuDatabaseNameHere (mind the leading jdbc: and the ql addition to postgres!)
  • SPRING_DATASOURCE_USERNAME = YourPostgresHerokuUserNameHere
  • SPRING_DATASOURCE_PASSWORD = YourPostgresHerokuPasswordHere
  • SPRING_DATASOURCE_DRIVER-CLASS-NAME= org.postgresql.Driver (this isn´t always needed since Spring Boot can deduce it for most databases from the url, just for completeness here)
  • SPRING_JPA_DATABASE-PLATFORM = org.hibernate.dialect.PostgreSQLDialect
  • SPRING_DATASOURCE_TYPE = org.apache.tomcat.jdbc.pool.DataSource
  • SPRING_JPA_HIBERNATE_DDL-AUTO = update (this will automatically create your tables according to your JPA entities, which is really great - since you don´t need to hurdle with CREATE SQL statements or DDL files)

In Heroku this should look like this:

heroku-spring-boot-app-postgres-config

Now that´s all you have to do! Your Heroku app is restarted every time you change a Config Variable - so your App should now run H2 locally, and should be ready connected with PostgreSQL when deployed on Heroku.

Just if you´re asking: Why do we configure Tomcat JDBC instead of Hikari

As you might noticed, we added the tomcat-jdbc dependency to our pom.xml and configured SPRING_DATASOURCE_TYPE=org.apache.tomcat.jdbc.pool.DataSource as a Environment variable. There´s only a slight hint in the docs about this saying

You can bypass that algorithm completely and specify the connection pool to use by setting the spring.datasource.type property. This is especially important if you run your application in a Tomcat container, ...

There are several reasons I switched back to Tomcat pooling DataSource instead of using the Spring Boot 2.x standard HikariCP. As I already explained here, if you don´t specifiy spring.datasource.url, Spring will try to autowire the embedded im-memory H2 database instead of our PostgreSQL one. And the problem with Hikari is, that it only supports spring.datasource.jdbc-url.

Second, if I try to use the Heroku configuration as shown for Hikari (so leaving out SPRING_DATASOURCE_TYPE and changing SPRING_DATASOURCE_URL to SPRING_DATASOURCE_JDBC-URL) I run into the following Exception:

Caused by: java.lang.RuntimeException: Driver org.postgresql.Driver claims to not accept jdbcUrl, jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

So I didn´t get Spring Boot 2.x working on Heroku & Postgres with HikariCP, but with Tomcat JDBC - and I also don´t want to brake my development process containing a local H2 database described upfront. Remember: We were looking for the simplest, cleanest way of connecting to Heroku Postgres in a Spring Boot app using JPA/Hibernate!


Simplest Spring Boot / Heroku / Hibernate Configuration

Apart from DATABASE_URL, which is always there, Heroku creates 3 environment variables at Runtime. They are:

JDBC_DATABASE_URLJDBC_DATABASE_USERNAMEJDBC_DATABASE_PASSWORD

As you may be aware, Spring Boot will automatically configure your database if it finds spring.datasource.* properties in your application.properties file. Here is an example of my application.properties

spring.datasource.url=${JDBC_DATABASE_URL}spring.datasource.username=${JDBC_DATABASE_USERNAME}spring.datasource.password=${JDBC_DATABASE_PASSWORD}spring.jpa.show-sql=falsespring.jpa.generate-ddl=truespring.jpa.hibernate.ddl-auto=update

Hibernate / Postgres Dependencies

In my case I'm using Hibernate (bundled in spring-boot-starter-jpa with PostgreSQL, so I needed the right dependencies in my build.gradle:

dependencies {    compile("org.springframework.boot:spring-boot-starter-data-jpa")    compile('org.postgresql:postgresql:9.4.1212')}


To get the database connection working (in a stable manner) two things were missing in the setup I described in the question:

  • As jny pointed out, I needed to set JDBC driver explicitly:
    • dataSource.setDriverClassName("org.postgresql.Driver");
    • (The reason for this is that I'm defining a custom datasource, overriding Spring's default, causing my spring.datasource.driverClassName property to have no effect. And to my understanding, due to the dynamic nature of Heroku's DATABASE_URL, I need custom datasource to make it work.)
  • After this the connection worked, but it wasn't stable; I started getting org.postgresql.util.PSQLException: This connection has been closed. after the app had been running for a while. A somewhat surprising solution (based on this answer) was to enable certain tests such as testOnBorrow on the Tomcat DataSource:
    • dataSource.setTestOnBorrow(true);dataSource.setTestWhileIdle(true);dataSource.setTestOnReturn(true);dataSource.setValidationQuery("SELECT 1");

So, the fixed version of my DataSourceConfig:

@Configurationpublic class DataSourceConfig {    Logger log = LoggerFactory.getLogger(getClass());    @Bean    @Profile("postgres")    public DataSource postgresDataSource() {        String databaseUrl = System.getenv("DATABASE_URL")        log.info("Initializing PostgreSQL database: {}", databaseUrl);        URI dbUri;        try {            dbUri = new URI(databaseUrl);        }        catch (URISyntaxException e) {            log.error(String.format("Invalid DATABASE_URL: %s", databaseUrl), e);            return null;        }        String username = dbUri.getUserInfo().split(":")[0];        String password = dbUri.getUserInfo().split(":")[1];        String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + ':'                        + dbUri.getPort() + dbUri.getPath();        org.apache.tomcat.jdbc.pool.DataSource dataSource             = new org.apache.tomcat.jdbc.pool.DataSource();        dataSource.setDriverClassName("org.postgresql.Driver");        dataSource.setUrl(dbUrl);        dataSource.setUsername(username);        dataSource.setPassword(password);        dataSource.setTestOnBorrow(true);        dataSource.setTestWhileIdle(true);        dataSource.setTestOnReturn(true);        dataSource.setValidationQuery("SELECT 1");        return dataSource;    }}

With only this in application-postgres.properties:

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

Now, both of the problems I had may be specific to the DataSource from Tomcat (org.apache.tomcat.jdbc.pool). Apparently BasicDataSource (Commons DBCP) has more sensible defaults. But as mentiond in the question, I rather used something that comes with Spring Boot by default, especially as it's strongly endorsed in the reference guide.

I'm open to competing / simpler / better solutions, so feel free to post, especially if you can address the doubts 2–4 at the end of the question!

Using JDBC_DATABASE_* variables instead

Update: Note that using JDBC_DATABASE_* is much simpler than the above, as pointed out in this answer. For a long time I was under the impression that DATABASE_URL should be preferred, but nowadays I'm not so sure anymore.