0
# Utilities
1
2
Supporting utility classes for credentials management, JNDI integration, and advanced connection handling.
3
4
## Capabilities
5
6
### Credentials Management
7
8
Immutable credentials holder for secure username/password management.
9
10
```java { .api }
11
/**
12
* Immutable credentials holder for database authentication
13
* Thread-safe and designed for use with atomic updates
14
*/
15
public final class Credentials {
16
17
/**
18
* Factory method to create credentials instance
19
* @param username Database username (can be null)
20
* @param password Database password (can be null)
21
* @return New Credentials instance
22
*/
23
public static Credentials of(String username, String password);
24
25
/**
26
* Constructor for credentials
27
* @param username Database username
28
* @param password Database password
29
*/
30
@ConstructorParameters({"username", "password"})
31
public Credentials(String username, String password);
32
33
/**
34
* Get the username
35
* @return Username string or null
36
*/
37
public String getUsername();
38
39
/**
40
* Get the password
41
* @return Password string or null
42
*/
43
public String getPassword();
44
}
45
```
46
47
**Credentials Usage Examples:**
48
49
```java
50
import com.zaxxer.hikari.util.Credentials;
51
import java.util.concurrent.atomic.AtomicReference;
52
53
// Basic credential creation
54
Credentials creds = Credentials.of("dbuser", "dbpass");
55
Credentials nullCreds = Credentials.of(null, null);
56
57
// Thread-safe credential updates (used internally by HikariConfig)
58
AtomicReference<Credentials> credentialsRef = new AtomicReference<>(
59
Credentials.of("olduser", "oldpass"));
60
61
// Atomic username update
62
credentialsRef.updateAndGet(current ->
63
Credentials.of("newuser", current.getPassword()));
64
65
// Atomic password update
66
credentialsRef.updateAndGet(current ->
67
Credentials.of(current.getUsername(), "newpass"));
68
69
// Atomic full update
70
credentialsRef.set(Credentials.of("admin", "secretpass"));
71
72
// Usage in configuration
73
HikariConfig config = new HikariConfig();
74
config.setCredentials(Credentials.of("appuser", "apppass"));
75
76
// Runtime credential updates (DataSource-based connections only)
77
HikariDataSource dataSource = new HikariDataSource(config);
78
dataSource.setCredentials(Credentials.of("newuser", "newpass"));
79
```
80
81
### JNDI Integration
82
83
JNDI ObjectFactory for creating HikariDataSource instances in application servers.
84
85
```java { .api }
86
/**
87
* JNDI ObjectFactory for creating HikariDataSource instances
88
* Enables deployment in Java EE application servers with JNDI configuration
89
*/
90
public class HikariJNDIFactory implements ObjectFactory {
91
92
/**
93
* Create HikariDataSource instance from JNDI Reference
94
* @param obj Reference object (must be javax.sql.DataSource Reference)
95
* @param name JNDI name being created
96
* @param nameCtx Naming context
97
* @param environment JNDI environment
98
* @return HikariDataSource instance or null if not applicable
99
* @throws Exception if creation fails
100
*/
101
@Override
102
public synchronized Object getObjectInstance(Object obj, Name name, Context nameCtx,
103
Hashtable<?, ?> environment) throws Exception;
104
}
105
```
106
107
**JNDI Configuration Examples:**
108
109
**Tomcat context.xml:**
110
111
```xml
112
<Context>
113
<Resource name="jdbc/MyDB"
114
factory="com.zaxxer.hikari.HikariJNDIFactory"
115
type="javax.sql.DataSource"
116
jdbcUrl="jdbc:mysql://localhost:3306/mydb"
117
username="dbuser"
118
password="dbpass"
119
maximumPoolSize="20"
120
minimumIdle="5"
121
connectionTimeout="30000"
122
idleTimeout="600000"
123
maxLifetime="1800000"
124
poolName="JNDI-Pool"
125
dataSource.cachePrepStmts="true"
126
dataSource.prepStmtCacheSize="250"
127
dataSource.useServerPrepStmts="true" />
128
</Context>
129
```
130
131
**WildFly/JBoss standalone.xml:**
132
133
```xml
134
<subsystem xmlns="urn:jboss:domain:naming:2.0">
135
<bindings>
136
<object-factory name="java:jboss/datasources/MyDB"
137
module="com.zaxxer.hikari"
138
class="com.zaxxer.hikari.HikariJNDIFactory">
139
<environment>
140
<property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/mydb"/>
141
<property name="username" value="dbuser"/>
142
<property name="password" value="dbpass"/>
143
<property name="maximumPoolSize" value="25"/>
144
<property name="minimumIdle" value="5"/>
145
<property name="poolName" value="WildFly-Pool"/>
146
<property name="registerMbeans" value="true"/>
147
</environment>
148
</object-factory>
149
</bindings>
150
</subsystem>
151
```
152
153
**JNDI Lookup Usage:**
154
155
```java
156
import javax.naming.InitialContext;
157
import javax.sql.DataSource;
158
159
public class DatabaseService {
160
161
private DataSource dataSource;
162
163
public void init() {
164
try {
165
InitialContext ctx = new InitialContext();
166
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
167
System.out.println("HikariDataSource obtained from JNDI");
168
} catch (Exception e) {
169
throw new RuntimeException("Failed to lookup DataSource", e);
170
}
171
}
172
173
public Connection getConnection() throws SQLException {
174
return dataSource.getConnection();
175
}
176
}
177
```
178
179
**JNDI with DataSource Reference:**
180
181
```xml
182
<!-- Alternative JNDI configuration using existing DataSource -->
183
<Resource name="jdbc/MyDB"
184
factory="com.zaxxer.hikari.HikariJNDIFactory"
185
type="javax.sql.DataSource"
186
dataSourceJNDI="java:comp/env/jdbc/UnderlyingDS"
187
maximumPoolSize="15"
188
minimumIdle="3"
189
poolName="Wrapped-Pool" />
190
```
191
192
### SQL Exception Override
193
194
Interface for customizing SQLException handling and connection eviction behavior.
195
196
```java { .api }
197
/**
198
* Interface for custom SQLException handling in HikariCP
199
* Allows overriding default connection eviction logic
200
*/
201
public interface SQLExceptionOverride {
202
203
/**
204
* Enumeration of eviction override decisions
205
*/
206
enum Override {
207
/** Continue with default HikariCP eviction logic */
208
CONTINUE_EVICT,
209
/** Do not evict the connection regardless of exception */
210
DO_NOT_EVICT,
211
/** Force eviction regardless of exception type */
212
MUST_EVICT
213
}
214
215
/**
216
* Adjudicate whether a connection should be evicted based on SQLException
217
* @param sqlException The SQLException that was thrown
218
* @return Override decision for connection eviction
219
*/
220
default Override adjudicate(SQLException sqlException);
221
}
222
```
223
224
**SQL Exception Override Examples:**
225
226
```java
227
import com.zaxxer.hikari.SQLExceptionOverride;
228
import java.sql.SQLException;
229
230
/**
231
* Custom exception override for database-specific error handling
232
*/
233
public class CustomSQLExceptionOverride implements SQLExceptionOverride {
234
235
@Override
236
public Override adjudicate(SQLException sqlException) {
237
String sqlState = sqlException.getSQLState();
238
int errorCode = sqlException.getErrorCode();
239
240
// MySQL-specific error handling
241
if (sqlState != null) {
242
switch (sqlState) {
243
case "08S01": // Communication link failure
244
case "08007": // Connection failure during transaction
245
case "08006": // Connection failure
246
return Override.MUST_EVICT;
247
248
case "40001": // Serialization failure (deadlock)
249
case "25006": // Read-only transaction
250
return Override.DO_NOT_EVICT; // Temporary condition
251
252
case "42000": // Syntax error - don't evict for application errors
253
case "23000": // Integrity constraint violation
254
return Override.DO_NOT_EVICT;
255
}
256
}
257
258
// MySQL error codes
259
switch (errorCode) {
260
case 1205: // Lock wait timeout - temporary condition
261
case 1213: // Deadlock - temporary condition
262
return Override.DO_NOT_EVICT;
263
264
case 2006: // MySQL server has gone away
265
case 2013: // Lost connection to MySQL server
266
return Override.MUST_EVICT;
267
}
268
269
// PostgreSQL-specific handling
270
if (sqlException.getMessage() != null) {
271
String message = sqlException.getMessage().toLowerCase();
272
if (message.contains("connection refused") ||
273
message.contains("connection reset") ||
274
message.contains("broken pipe")) {
275
return Override.MUST_EVICT;
276
}
277
}
278
279
// Default to HikariCP's built-in logic
280
return Override.CONTINUE_EVICT;
281
}
282
}
283
284
// Configuration usage
285
HikariConfig config = new HikariConfig();
286
config.setExceptionOverrideClassName("com.myapp.CustomSQLExceptionOverride");
287
288
// Or set instance directly
289
config.setExceptionOverride(new CustomSQLExceptionOverride());
290
```
291
292
**Advanced Exception Override:**
293
294
```java
295
import org.slf4j.Logger;
296
import org.slf4j.LoggerFactory;
297
298
public class ProductionSQLExceptionOverride implements SQLExceptionOverride {
299
300
private static final Logger logger = LoggerFactory.getLogger(ProductionSQLExceptionOverride.class);
301
302
@Override
303
public Override adjudicate(SQLException sqlException) {
304
// Log all exceptions for monitoring
305
logger.warn("SQLException in connection pool - State: {}, Code: {}, Message: {}",
306
sqlException.getSQLState(), sqlException.getErrorCode(), sqlException.getMessage());
307
308
// Check for specific patterns that indicate connection issues
309
if (isConnectionFailure(sqlException)) {
310
logger.error("Connection failure detected, forcing eviction", sqlException);
311
return Override.MUST_EVICT;
312
}
313
314
// Check for temporary conditions that shouldn't cause eviction
315
if (isTemporaryCondition(sqlException)) {
316
logger.info("Temporary condition detected, preserving connection");
317
return Override.DO_NOT_EVICT;
318
}
319
320
// Use default HikariCP logic for other cases
321
return Override.CONTINUE_EVICT;
322
}
323
324
private boolean isConnectionFailure(SQLException e) {
325
String sqlState = e.getSQLState();
326
int errorCode = e.getErrorCode();
327
String message = e.getMessage();
328
329
// SQLSTATE classes indicating connection problems
330
if (sqlState != null && (sqlState.startsWith("08") || sqlState.startsWith("57"))) {
331
return true;
332
}
333
334
// Common database error codes for connection issues
335
if (errorCode == 2006 || errorCode == 2013 || // MySQL
336
errorCode == 17002 || errorCode == 17008 || // Oracle
337
errorCode == -4499 || errorCode == -30108) { // DB2
338
return true;
339
}
340
341
// Message-based detection
342
if (message != null) {
343
String lowerMessage = message.toLowerCase();
344
return lowerMessage.contains("connection") &&
345
(lowerMessage.contains("closed") || lowerMessage.contains("reset") ||
346
lowerMessage.contains("refused") || lowerMessage.contains("timeout"));
347
}
348
349
return false;
350
}
351
352
private boolean isTemporaryCondition(SQLException e) {
353
String sqlState = e.getSQLState();
354
int errorCode = e.getErrorCode();
355
356
// Serialization failures, deadlocks - retry without eviction
357
if ("40001".equals(sqlState) || "40P01".equals(sqlState)) {
358
return true;
359
}
360
361
// Lock timeouts
362
if (errorCode == 1205 || errorCode == 1213) { // MySQL
363
return true;
364
}
365
366
return false;
367
}
368
}
369
```
370
371
### Utility Integration
372
373
Integrate utility classes with HikariCP configuration and monitoring.
374
375
```java
376
import com.zaxxer.hikari.HikariConfig;
377
import com.zaxxer.hikari.HikariDataSource;
378
import com.zaxxer.hikari.util.Credentials;
379
380
public class DatabaseConnectionManager {
381
382
private HikariDataSource primaryDataSource;
383
private HikariDataSource readOnlyDataSource;
384
385
public void initialize() {
386
// Primary database with custom exception handling
387
HikariConfig primaryConfig = createBaseConfig();
388
primaryConfig.setJdbcUrl("jdbc:mysql://primary-db:3306/myapp");
389
primaryConfig.setCredentials(Credentials.of("app_user", "app_pass"));
390
primaryConfig.setExceptionOverride(new ProductionSQLExceptionOverride());
391
primaryConfig.setPoolName("Primary-DB");
392
primaryDataSource = new HikariDataSource(primaryConfig);
393
394
// Read-only replica
395
HikariConfig readOnlyConfig = createBaseConfig();
396
readOnlyConfig.setJdbcUrl("jdbc:mysql://readonly-db:3306/myapp");
397
readOnlyConfig.setCredentials(Credentials.of("readonly_user", "readonly_pass"));
398
readOnlyConfig.setReadOnly(true);
399
readOnlyConfig.setPoolName("ReadOnly-DB");
400
readOnlyDataSource = new HikariDataSource(readOnlyConfig);
401
}
402
403
private HikariConfig createBaseConfig() {
404
HikariConfig config = new HikariConfig();
405
config.setMaximumPoolSize(20);
406
config.setMinimumIdle(5);
407
config.setConnectionTimeout(30000);
408
config.setIdleTimeout(600000);
409
config.setMaxLifetime(1800000);
410
config.setLeakDetectionThreshold(60000);
411
config.setRegisterMbeans(true);
412
413
// MySQL optimizations
414
config.addDataSourceProperty("cachePrepStmts", "true");
415
config.addDataSourceProperty("prepStmtCacheSize", "250");
416
config.addDataSourceProperty("useServerPrepStmts", "true");
417
418
return config;
419
}
420
421
public Connection getPrimaryConnection() throws SQLException {
422
return primaryDataSource.getConnection();
423
}
424
425
public Connection getReadOnlyConnection() throws SQLException {
426
return readOnlyDataSource.getConnection();
427
}
428
429
public void updateCredentials(String username, String password) {
430
Credentials newCreds = Credentials.of(username, password);
431
primaryDataSource.setCredentials(newCreds);
432
// Note: credential updates only work with DataSource-based connections
433
}
434
435
public void shutdown() {
436
if (primaryDataSource != null) {
437
primaryDataSource.close();
438
}
439
if (readOnlyDataSource != null) {
440
readOnlyDataSource.close();
441
}
442
}
443
}
444
```
445
446
### Advanced Utility Patterns
447
448
Advanced patterns using HikariCP utilities for enterprise scenarios.
449
450
```java
451
import javax.naming.InitialContext;
452
import java.util.concurrent.ConcurrentHashMap;
453
import java.util.Map;
454
455
/**
456
* Multi-tenant database connection manager using HikariCP utilities
457
*/
458
public class MultiTenantConnectionManager {
459
460
private final Map<String, HikariDataSource> tenantDataSources = new ConcurrentHashMap<>();
461
private final SQLExceptionOverride commonExceptionOverride = new ProductionSQLExceptionOverride();
462
463
/**
464
* Get or create DataSource for specific tenant
465
*/
466
public DataSource getDataSource(String tenantId) {
467
return tenantDataSources.computeIfAbsent(tenantId, this::createTenantDataSource);
468
}
469
470
private HikariDataSource createTenantDataSource(String tenantId) {
471
try {
472
// Try JNDI lookup first
473
InitialContext ctx = new InitialContext();
474
String jndiName = "java:comp/env/jdbc/" + tenantId;
475
476
try {
477
return (HikariDataSource) ctx.lookup(jndiName);
478
} catch (Exception e) {
479
// JNDI lookup failed, create programmatically
480
return createProgrammaticDataSource(tenantId);
481
}
482
483
} catch (Exception e) {
484
throw new RuntimeException("Failed to create DataSource for tenant: " + tenantId, e);
485
}
486
}
487
488
private HikariDataSource createProgrammaticDataSource(String tenantId) {
489
HikariConfig config = new HikariConfig();
490
491
// Tenant-specific configuration
492
TenantConfig tenantConfig = getTenantConfig(tenantId);
493
config.setJdbcUrl(tenantConfig.getJdbcUrl());
494
config.setCredentials(Credentials.of(tenantConfig.getUsername(), tenantConfig.getPassword()));
495
496
// Common pool settings
497
config.setMaximumPoolSize(tenantConfig.getMaxPoolSize());
498
config.setMinimumIdle(tenantConfig.getMinIdle());
499
config.setConnectionTimeout(30000);
500
config.setIdleTimeout(600000);
501
config.setMaxLifetime(1800000);
502
config.setPoolName("Tenant-" + tenantId);
503
config.setRegisterMbeans(true);
504
505
// Common exception handling
506
config.setExceptionOverride(commonExceptionOverride);
507
508
return new HikariDataSource(config);
509
}
510
511
/**
512
* Update credentials for all tenants (rolling update)
513
*/
514
public void updateCredentials(Map<String, Credentials> tenantCredentials) {
515
tenantCredentials.forEach((tenantId, credentials) -> {
516
HikariDataSource ds = tenantDataSources.get(tenantId);
517
if (ds != null) {
518
ds.setCredentials(credentials);
519
System.out.printf("Updated credentials for tenant: %s%n", tenantId);
520
}
521
});
522
}
523
524
/**
525
* Shutdown all tenant connections
526
*/
527
public void shutdown() {
528
tenantDataSources.values().forEach(HikariDataSource::close);
529
tenantDataSources.clear();
530
}
531
532
private TenantConfig getTenantConfig(String tenantId) {
533
// Load tenant-specific configuration from external source
534
// This is implementation-specific
535
return new TenantConfig(tenantId);
536
}
537
538
private static class TenantConfig {
539
private final String tenantId;
540
541
public TenantConfig(String tenantId) {
542
this.tenantId = tenantId;
543
}
544
545
public String getJdbcUrl() {
546
return "jdbc:mysql://db-" + tenantId + ":3306/" + tenantId;
547
}
548
549
public String getUsername() {
550
return tenantId + "_user";
551
}
552
553
public String getPassword() {
554
return loadPasswordFromVault(tenantId);
555
}
556
557
public int getMaxPoolSize() {
558
return "premium".equals(getTenantTier()) ? 20 : 10;
559
}
560
561
public int getMinIdle() {
562
return "premium".equals(getTenantTier()) ? 5 : 2;
563
}
564
565
// Implementation-specific methods
566
private String loadPasswordFromVault(String tenantId) { return "password"; }
567
private String getTenantTier() { return "standard"; }
568
}
569
}