Quick Start: Java and TimescaleDB

Goal

This quick start guide is designed to get Java developers up and running with TimescaleDB as their database. In this tutorial, you’ll learn how to:

Pre-requisites

To complete this tutorial, you will need a cursory knowledge of the Structured Query Language (SQL). The tutorial will walk you through each SQL command, but it will be helpful if you’ve seen SQL before.

To start, install TimescaleDB. Once your installation is complete, you can proceed to ingesting or creating sample data and finishing the tutorial.

You will also need to install Java Development Kit (JDK) and PostgreSQL Java Database Connectivity (JDBC) Driver as well. All code is presented for Java 16 and above. If you are working with older JDK versions, use legacy coding techniques.

Connect Java to TimescaleDB

Step 1: Create a new Java application

For simplicity, we will use the application in a single file as an example. You can use any of your favorite build tools, including gradle and maven.

Create a separate directory and navigate to it. In it, create a text file with name and extension Main.java and the following content:

  1. package com.timescale.java;
  2. public class Main {
  3. public static void main(String... args) {
  4. System.out.println("Hello, World!");
  5. }
  6. }

From the command line in the current directory, try running the application with this command:

  1. java Main.java

You should see the Hello, World! line output to your console. In case of an error, refer to the documentation and check if the JDK was installed correctly. You don’t have to create directory structure ./com/timescale/java similar to package path in source file. You should just create a single java file in empty folder and run java Main.java from it.

Step 2: Import Postgres JDBC driver

To work with the PostgreSQL, you need to import the appropriate JDBC Driver. If you are using a dependency manager, include PostgreSQL JDBC Driver as dependency. In this case, download jar artifact of JDBC Driver and place it next to the Main.java file.

Now you can import the JDBC Driver into the Java application and display a list of available drivers for the check:

  1. package com.timescale.java;
  2. import java.sql.DriverManager;
  3. public class Main {
  4. public static void main(String... args) {
  5. DriverManager.drivers().forEach(System.out::println);
  6. }
  7. }

Use this command to run all the following examples:

  1. java -cp *.jar Main.java

You should end up with something like [[email protected]](https://docs.timescale.com/cdn-cgi/l/email-protection). This means that you are ready to connect to TimescaleDB from Java.

Step 3: Compose a database connection string

Locate your TimescaleDB credentials. You need these to compose a connection string for JDBC to use to connect to your TimescaleDB instance.

You’ll need these credentials:

  • host
  • port
  • database name
  • username
  • password

Next, compose your connection string variable using this format:

  1. var connUrl = "jdbc:postgresql://host:port/dbname?user=username&password=password";

Full documentation on the formation of the connection string can be found in the official documentation of the PostgreSQL JDBC Driver.

warning

The above method of composing a connection string is for test or development purposes only, for production purposes be sure to make sensitive details like your password, hostname, and port number environment variables.

Step 4: Connect to TimescaleDB instance using the PostgreSQL JDBC driver

Change the code to connect to the database server and verify that all settings are correct:

  1. package com.timescale.java;
  2. import java.sql.DriverManager;
  3. import java.sql.SQLException;
  4. public class Main {
  5. public static void main(String... args) throws SQLException {
  6. var connUrl = "jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres";
  7. var conn = DriverManager.getConnection(connUrl);
  8. System.out.println(conn.getClientInfo());
  9. }
  10. }

Run with the java -cp *.jar Main.java command and you should see this output: {ApplicationName=PostgreSQL JDBC Driver}.

Congratulations, you’ve successfully connected to TimescaleDB using Java.

Create a relational table

Step 1: Formulate your SQL statement

First, compose a string which contains the SQL state that you would use to create a relational table. In this example, we create a table called sensors, with columns id, type and location:

  1. CREATE TABLE sensors (
  2. id SERIAL PRIMARY KEY,
  3. type TEXT NOT NULL,
  4. location TEXT NOT NULL
  5. );

Step 2: Execute the SQL statement and commit changes

Next, execute the CREATE TABLE query by creating a statement, executing the query from Step 1 and check that table was created with SELECT statement:

  1. package com.timescale.java;
  2. import java.sql.DriverManager;
  3. import java.sql.SQLException;
  4. public class Main {
  5. public static void main(String... args) throws SQLException {
  6. var connUrl = "jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres";
  7. var conn = DriverManager.getConnection(connUrl);
  8. var createSensorTableQuery = """
  9. CREATE TABLE sensors (
  10. id SERIAL PRIMARY KEY,
  11. type TEXT NOT NULL,
  12. location TEXT NOT NULL
  13. )
  14. """;
  15. try (var stmt = conn.createStatement()) {
  16. stmt.execute(createSensorTableQuery);
  17. }
  18. var showAllTablesQuery = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public'";
  19. try (var stmt = conn.createStatement();
  20. var rs = stmt.executeQuery(showAllTablesQuery)) {
  21. System.out.println("Tables in the current database: ");
  22. while (rs.next()) {
  23. System.out.println(rs.getString("tablename"));
  24. }
  25. }
  26. }
  27. }

Congratulations, you’ve successfully created a relational table in TimescaleDB using Java.

Generate a hypertable

In TimescaleDB, the primary point of interaction with your data is a hypertable, the abstraction of a single continuous table across all space and time intervals, such that one can query it via standard SQL.

Virtually all user interactions with TimescaleDB are with hypertables. Creating tables and indexes, altering tables, inserting data, and selecting data, can (and should) all be executed on the hypertable.

A hypertable is defined by a standard schema with column names and types, with at least one column specifying a time value.

Step 1: Create sensors data table

First, we create CREATE TABLE SQL statement for our hypertable. Notice how the hypertable has the compulsory time column:

  1. CREATE TABLE sensor_data (
  2. time TIMESTAMPTZ NOT NULL,
  3. sensor_id INTEGER REFERENCES sensors (id),
  4. value DOUBLE PRECISION
  5. );

Step 2: Create hypertable for sensors data

Next, you can formulate the SELECT statement to convert the table we created in Step 1 into a hypertable. Note that you must specify the table name to convert to a hypertable and its time column name as the two arguments, as mandated by the create_hypertable docs:

  1. SELECT create_hypertable('sensor_data', 'time');

Step 3: Execute previous steps from your Java code

Now you can bring it all together by executing the statement from step 1, then executing the statement from step 2 and committing your changes to the database:

  1. package com.timescale.java;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.SQLException;
  5. import java.util.List;
  6. public class Main {
  7. public static void main(String... args) {
  8. final var connUrl = "jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres";
  9. try (var conn = DriverManager.getConnection(connUrl)) {
  10. createSchema(conn);
  11. insertData(conn);
  12. } catch (SQLException ex) {
  13. System.err.println(ex.getMessage());
  14. }
  15. }
  16. private static void createSchema(final Connection conn) throws SQLException {
  17. try (var stmt = conn.createStatement()) {
  18. stmt.execute("""
  19. CREATE TABLE sensors (
  20. id SERIAL PRIMARY KEY,
  21. type TEXT NOT NULL,
  22. location TEXT NOT NULL
  23. )
  24. """);
  25. }
  26. try (var stmt = conn.createStatement()) {
  27. stmt.execute("""
  28. CREATE TABLE sensor_data (
  29. time TIMESTAMPTZ NOT NULL,
  30. sensor_id INTEGER REFERENCES sensors (id),
  31. value DOUBLE PRECISION
  32. )
  33. """);
  34. }
  35. try (var stmt = conn.createStatement()) {
  36. stmt.execute("SELECT create_hypertable('sensor_data', 'time')");
  37. }
  38. }
  39. }

Congratulations, you’ve successfully created a hypertable in your TimescaleDB database using Java.

Insert a batch of rows into TimescaleDB

Here’s a typical pattern you could use to insert some data into a table. In the example below, insert the relational data from list of sensors, into the relational table named sensors.

First, open a connection to the database, then using prepared statements formulate the INSERT SQL statement and then execute that statement:

  1. final List<Sensor> sensors = List.of(
  2. new Sensor("temperature", "bedroom"),
  3. new Sensor("temperature", "living room"),
  4. new Sensor("temperature", "outside"),
  5. new Sensor("humidity", "kitchen"),
  6. new Sensor("humidity", "outside"));
  7. for (final var sensor : sensors) {
  8. try (var stmt = conn.prepareStatement("INSERT INTO sensors (type, location) VALUES (?, ?)")) {
  9. stmt.setString(1, sensor.type());
  10. stmt.setString(2, sensor.location());
  11. stmt.executeUpdate();
  12. }
  13. }

You can insert a batch of rows into TimescaleDB in a couple of different ways. Let’s see what it looks like to insert a number of rows with batching mechanism. For simplicity’s sake, we’ll use PostgreSQL to generate some sample time-series data in order to insert into the sensor_data hypertable:

  1. final var sensorDataCount = 100;
  2. final var insertBatchSize = 10;
  3. try (var stmt = conn.prepareStatement("""
  4. INSERT INTO sensor_data (time, sensor_id, value)
  5. VALUES (
  6. generate_series(now() - INTERVAL '24 hours', now(), INTERVAL '5 minutes'),
  7. floor(random() * 4 + 1)::INTEGER,
  8. random()
  9. )
  10. """)) {
  11. for (int i = 0; i < sensorDataCount; i++) {
  12. stmt.addBatch();
  13. if ((i > 0 && i % insertBatchSize == 0) || i == sensorDataCount - 1) {
  14. stmt.executeBatch();
  15. }
  16. }
  17. }

Below is a complete listing of the application, from creating tables to filling in the data:

  1. package com.timescale.java;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.SQLException;
  5. import java.util.List;
  6. public class Main {
  7. public static void main(String... args) {
  8. final var connUrl = "jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres";
  9. try (var conn = DriverManager.getConnection(connUrl)) {
  10. createSchema(conn);
  11. insertData(conn);
  12. } catch (SQLException ex) {
  13. System.err.println(ex.getMessage());
  14. }
  15. }
  16. private static void createSchema(final Connection conn) throws SQLException {
  17. try (var stmt = conn.createStatement()) {
  18. stmt.execute("""
  19. CREATE TABLE sensors (
  20. id SERIAL PRIMARY KEY,
  21. type TEXT NOT NULL,
  22. location TEXT NOT NULL
  23. )
  24. """);
  25. }
  26. try (var stmt = conn.createStatement()) {
  27. stmt.execute("""
  28. CREATE TABLE sensor_data (
  29. time TIMESTAMPTZ NOT NULL,
  30. sensor_id INTEGER REFERENCES sensors (id),
  31. value DOUBLE PRECISION
  32. )
  33. """);
  34. }
  35. try (var stmt = conn.createStatement()) {
  36. stmt.execute("SELECT create_hypertable('sensor_data', 'time')");
  37. }
  38. }
  39. private static void insertData(final Connection conn) throws SQLException {
  40. final List<Sensor> sensors = List.of(
  41. new Sensor("temperature", "bedroom"),
  42. new Sensor("temperature", "living room"),
  43. new Sensor("temperature", "outside"),
  44. new Sensor("humidity", "kitchen"),
  45. new Sensor("humidity", "outside"));
  46. for (final var sensor : sensors) {
  47. try (var stmt = conn.prepareStatement("INSERT INTO sensors (type, location) VALUES (?, ?)")) {
  48. stmt.setString(1, sensor.type());
  49. stmt.setString(2, sensor.location());
  50. stmt.executeUpdate();
  51. }
  52. }
  53. final var sensorDataCount = 100;
  54. final var insertBatchSize = 10;
  55. try (var stmt = conn.prepareStatement("""
  56. INSERT INTO sensor_data (time, sensor_id, value)
  57. VALUES (
  58. generate_series(now() - INTERVAL '24 hours', now(), INTERVAL '5 minutes'),
  59. floor(random() * 4 + 1)::INTEGER,
  60. random()
  61. )
  62. """)) {
  63. for (int i = 0; i < sensorDataCount; i++) {
  64. stmt.addBatch();
  65. if ((i > 0 && i % insertBatchSize == 0) || i == sensorDataCount - 1) {
  66. stmt.executeBatch();
  67. }
  68. }
  69. }
  70. }
  71. private record Sensor(String type, String location) {
  72. }
  73. }

tip

If you are inserting data from a CSV file, we recommend the timescale-parallel-copy tool, which is a command line program for parallelizing PostgreSQL’s built-in COPY functionality for bulk inserting data into TimescaleDB.

Congratulations, you’ve successfully inserted data into TimescaleDB using Java.

Execute queries on TimescaleDB

Step 1: Define the SQL query

First, define the SQL query you’d like to run on the database. The example below contains a query which combines time-series and relational data. It returns the average values for every 15 minute interval for sensors with specific type and location.

  1. SELECT time_bucket('15 minutes', time) AS bucket, avg(value)
  2. FROM sensor_data
  3. JOIN sensors ON sensors.id = sensor_data.sensor_id
  4. WHERE sensors.type = ? AND sensors.location = ?
  5. GROUP BY bucket
  6. ORDER BY bucket DESC;

Notice the use of placeholders for sensor type and location.

Step 2: Execute the query

Now you can execute the query with the prepared statement and read out the result set for all a-type sensors located on the floor:

  1. try (var stmt = conn.prepareStatement("""
  2. SELECT time_bucket('15 minutes', time) AS bucket, avg(value)
  3. FROM sensor_data
  4. JOIN sensors ON sensors.id = sensor_data.sensor_id
  5. WHERE sensors.type = ? AND sensors.location = ?
  6. GROUP BY bucket
  7. ORDER BY bucket DESC
  8. """)) {
  9. stmt.setString(1, "temperature");
  10. stmt.setString(2, "living room");
  11. try (var rs = stmt.executeQuery()) {
  12. while (rs.next()) {
  13. System.out.printf("%s: %f%n", rs.getTimestamp(1), rs.getDouble(2));
  14. }
  15. }
  16. }

After executing the statement, you should see something like this in the console:

  1. 2021-05-12 23:30:00.0: 0,508649
  2. 2021-05-12 23:15:00.0: 0,477852
  3. 2021-05-12 23:00:00.0: 0,462298
  4. 2021-05-12 22:45:00.0: 0,457006
  5. 2021-05-12 22:30:00.0: 0,568744
  6. ...

Complete Java snippet for working with TimescaleDB

  1. package com.timescale.java;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.SQLException;
  5. import java.util.List;
  6. public class Main {
  7. public static void main(String... args) {
  8. final var connUrl = "jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres";
  9. try (var conn = DriverManager.getConnection(connUrl)) {
  10. createSchema(conn);
  11. insertData(conn);
  12. executeQueries(conn);
  13. } catch (SQLException ex) {
  14. System.err.println(ex.getMessage());
  15. }
  16. }
  17. private static void createSchema(final Connection conn) throws SQLException {
  18. try (var stmt = conn.createStatement()) {
  19. stmt.execute("""
  20. CREATE TABLE sensors (
  21. id SERIAL PRIMARY KEY,
  22. type TEXT NOT NULL,
  23. location TEXT NOT NULL
  24. )
  25. """);
  26. }
  27. try (var stmt = conn.createStatement()) {
  28. stmt.execute("""
  29. CREATE TABLE sensor_data (
  30. time TIMESTAMPTZ NOT NULL,
  31. sensor_id INTEGER REFERENCES sensors (id),
  32. value DOUBLE PRECISION
  33. )
  34. """);
  35. }
  36. try (var stmt = conn.createStatement()) {
  37. stmt.execute("SELECT create_hypertable('sensor_data', 'time')");
  38. }
  39. }
  40. private static void insertData(final Connection conn) throws SQLException {
  41. final List<Sensor> sensors = List.of(
  42. new Sensor("temperature", "bedroom"),
  43. new Sensor("temperature", "living room"),
  44. new Sensor("temperature", "outside"),
  45. new Sensor("humidity", "kitchen"),
  46. new Sensor("humidity", "outside"));
  47. for (final var sensor : sensors) {
  48. try (var stmt = conn.prepareStatement("INSERT INTO sensors (type, location) VALUES (?, ?)")) {
  49. stmt.setString(1, sensor.type());
  50. stmt.setString(2, sensor.location());
  51. stmt.executeUpdate();
  52. }
  53. }
  54. final var sensorDataCount = 100;
  55. final var insertBatchSize = 10;
  56. try (var stmt = conn.prepareStatement("""
  57. INSERT INTO sensor_data (time, sensor_id, value)
  58. VALUES (
  59. generate_series(now() - INTERVAL '24 hours', now(), INTERVAL '5 minutes'),
  60. floor(random() * 4 + 1)::INTEGER,
  61. random()
  62. )
  63. """)) {
  64. for (int i = 0; i < sensorDataCount; i++) {
  65. stmt.addBatch();
  66. if ((i > 0 && i % insertBatchSize == 0) || i == sensorDataCount - 1) {
  67. stmt.executeBatch();
  68. }
  69. }
  70. }
  71. }
  72. private static void executeQueries(final Connection conn) throws SQLException {
  73. try (var stmt = conn.prepareStatement("""
  74. SELECT time_bucket('15 minutes', time) AS bucket, avg(value)
  75. FROM sensor_data
  76. JOIN sensors ON sensors.id = sensor_data.sensor_id
  77. WHERE sensors.type = ? AND sensors.location = ?
  78. GROUP BY bucket
  79. ORDER BY bucket DESC
  80. """)) {
  81. stmt.setString(1, "temperature");
  82. stmt.setString(2, "living room");
  83. try (var rs = stmt.executeQuery()) {
  84. while (rs.next()) {
  85. System.out.printf("%s: %f%n", rs.getTimestamp(1), rs.getDouble(2));
  86. }
  87. }
  88. }
  89. }
  90. private record Sensor(String type, String location) {
  91. }
  92. }

Congratulations 🎉, you’ve successfully executed a query on TimescaleDB using Java and PostgreSQL JDBC!

Next steps

Now that you’re able to connect, read, and write to a TimescaleDB instance from your Java application, be sure to check out these advanced tutorials: