Hook: Interviewers love this question because it tests the invisible part of production performance: the thing that quietly makes every database call fast or slow.
Question: What is HikariCP?
Answer: HikariCP is a fast JDBC connection pool used by Spring Boot to reuse database connections instead of creating a new one for every query. JDBC means Java Database Connectivity, the standard Java API for talking to relational databases. HikariCP keeps a small set of ready-to-use connections, which lowers latency and protects the database from too many open sessions.
Interview-Ready Answer: I’d say HikariCP is the connection pool I expect to see behind most Spring Boot database work. It manages reusable JDBC connections so my code can borrow one, use it, and return it quickly instead of opening a brand-new database session each time. That cuts overhead, improves throughput, and helps avoid database overload. A nice production detail is that HikariCP is lightweight and very fast, which is why Spring Boot commonly picks it automatically when it is on the classpath.
Detailed Explanation: HikariCP is a JDBC connection pool. A connection pool is a managed cache of open database connections. Opening a real DB connection is expensive because it may involve TCP setup, authentication, SSL, and session creation. HikariCP keeps a small, warm set of connections ready so your app can borrow one in milliseconds instead of paying that setup cost for every query.
HikariDataSource when HikariCP is on the classpath and no other pool is chosen. A DataSource is the JDBC interface that hands out connections.getConnection() through JDBC, Spring JDBC, or Hibernate-style persistence code.maximumPoolSize is reached.close(), the physical DB session is usually not closed; it is returned to the pool for reuse.maxLifetime, and can log a warning if a connection is borrowed too long. That warning is leak detection, meaning the pool suspects you forgot to close a connection.connectionTimeout (default 30000 ms). If nothing becomes free, Hikari throws an exception instead of hanging forever.Use HikariCP whenever your Spring Boot app talks to a relational database through JDBC. It is the normal production choice because it is fast, small, and predictable. It protects the database by limiting concurrent sessions and it reduces latency by reusing connections. In other words: fewer expensive opens, more cheap reuses.
maximumPoolSize: usually the first setting to inspect; common starting values are 10 to 30, not hundreds.connectionTimeout: how long callers wait for a free connection; default 30 seconds.idleTimeout: default 10 minutes.maxLifetime: default 30 minutes; set it a little shorter than the database server’s own timeout.A useful mental model is this: the pool is a parking garage, not an infinite highway. If you add too many cars, the garage becomes crowded and slower to move through. A bigger pool is not always faster because the database still has to do the real work, and extra connections can increase contention instead of reducing it.
| Pool | Strength | Trade-off | Typical use |
|---|---|---|---|
| HikariCP | Fast | Few extras | Most Boot apps |
| DBCP2 | Stable | Heavier | Older systems |
| C3P0 | Simple | Slower | Legacy apps |
From a complexity point of view, the common borrow and return path is effectively O(1), and the space cost is roughly one live DB session per pooled connection. That is why pool size matters: each extra connection consumes memory and database resources. If the app holds connections during long transactions or slow network calls, throughput drops quickly, even if CPU looks idle.
One gotcha is that closing a pooled connection does not mean “kill the DB session.” It means “return it for reuse.” Another gotcha is stale connections: if your database closes sessions after 15 minutes, but Hikari keeps them for 30 minutes, you can see broken connections unless maxLifetime is tuned below the server timeout.
Real-World Story: Imagine a checkout service during a flash sale. Each request needs a few DB calls: reserve inventory, write the order, record payment state. The team leaves HikariCP at a tiny pool size from development, or one code path forgets to close a connection. Under load, requests pile up, threads wait for a connection, and users see spinning checkouts. Logs show Connection is not available, request timed out after 30000ms, while the database is not even maxed out. The root cause is usually a leak or an undersized pool: a connection was borrowed and never returned, or a long transaction held it too long. Hikari makes the problem visible quickly, but it cannot fix broken usage by itself.
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
@SpringBootApplication
public class HikariCpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HikariCpDemoApplication.class, args);
}
@Bean
HikariDataSource dataSource() {
// HikariCP is a DataSource. We configure it manually here so the demo runs
// without needing an external application.properties file.
HikariConfig config = new HikariConfig();
config.setPoolName("InterviewPool");
config.setJdbcUrl("jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1");
config.setUsername("sa");
config.setPassword("");
config.setDriverClassName("org.h2.Driver");
config.setMaximumPoolSize(2); // Small on purpose so pool exhaustion is easy to see.
config.setMinimumIdle(1);
config.setConnectionTimeout(2000); // Fast failure is useful in demos and in production alerts.
config.setIdleTimeout(10_000);
config.setMaxLifetime(30_000);
return new HikariDataSource(config);
}
@Bean
CommandLineRunner initDatabase(JdbcTemplate jdbcTemplate, HikariDataSource dataSource) {
return args -> {
jdbcTemplate.execute("CREATE TABLE products (id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL)");
jdbcTemplate.update("INSERT INTO products (id, name) VALUES (?, ?)", 1L, "Keyboard");
jdbcTemplate.update("INSERT INTO products (id, name) VALUES (?, ?)", 2L, "Mouse");
System.out.println("Seed data: " + jdbcTemplate.queryForList("SELECT id, name FROM products ORDER BY id"));
// Edge case: exhaust the pool.
// Two connections are borrowed and kept open, so the third request waits until
// connectionTimeout and then fails instead of hanging forever.
try (Connection c1 = dataSource.getConnection();
Connection c2 = dataSource.getConnection()) {
System.out.println("Borrowed two connections from a pool of size 2.");
try {
dataSource.getConnection();
System.out.println("Unexpected: a third connection was granted.");
} catch (SQLException ex) {
System.out.println("Expected pool exhaustion: " + ex.getMessage());
}
}
};
}
@RestController
@RequestMapping("/products")
static class ProductController {
private final JdbcTemplate jdbcTemplate;
private final HikariDataSource dataSource;
ProductController(JdbcTemplate jdbcTemplate, HikariDataSource dataSource) {
this.jdbcTemplate = jdbcTemplate;
this.dataSource = dataSource;
}
@GetMapping
List<Map<String, Object>> all() {
return jdbcTemplate.queryForList("SELECT id, name FROM products ORDER BY id");
}
@GetMapping("/{id}")
Map<String, Object> byId(@PathVariable long id) {
List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT id, name FROM products WHERE id = ?", id);
if (rows.isEmpty()) {
// A good failure path is explicit: callers get a clear 404 instead of a vague null.
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
}
return rows.get(0);
}
@GetMapping("/stats")
Map<String, Object> stats() {
HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();
if (pool == null) {
return Map.of("status", "pool not ready");
}
return Map.of(
"active", pool.getActiveConnections(),
"idle", pool.getIdleConnections(),
"total", pool.getTotalConnections(),
"waitingThreads", pool.getThreadsAwaitingConnection()
);
}
}
}
Follow-up & Tricky Questions:
maximumPoolSize, then check connectionTimeout and maxLifetime. Pool size should match real database capacity, not guesswork.close(). Hikari can warn about this with leak detection, which usually points straight to the bad code path.Connection is not available. If the database is healthy but callers are blocked, the pool size or connection lifetime is often the first thing to inspect.close() really close the database connection? In a pool, close() normally returns the connection to HikariCP instead of physically dropping the DB session. That is the whole benefit of pooling: reuse without paying the full reconnect cost.Common Mistakes:
close() with a real disconnect. Correction: in a pool, close() usually returns the connection to reuse.Memory Hook: Think of HikariCP as a valet parking garage for database connections: you hand over the keys, get a car quickly when you need it, and return it when you are done. You are not buying new cars every trip; you are reusing the same fleet.
Cheat Sheet:
maximumPoolSize, connectionTimeout, idleTimeout, and maxLifetime.Practice Tasks:
maximumPoolSize to 1 or 2 and observe how requests start waiting or timing out.