Resources and Dependency Injection
Closeable Resources
Section titled “Closeable Resources”Prefer Kotlin’s Closeable.use { ... } (and the AutoCloseable variant) over
an explicit try { ... } finally { resource.close() } for any object whose
lifetime ends at the bottom of the current scope.
use is strictly safer than the equivalent try/finally:
- If both the body and
close()throw,usepropagates the body’s exception and attaches the close-time exception asThrowable.addSuppressed(...). The naïvetry/finallyform throws whichever exception fires last, silently dropping the body’s exception — usually the one the caller actually needs. - It is shorter, removes the need for a separate variable, and makes the resource’s lifetime obvious at a glance.
// WRONG — body exception is lost if close() also throwsval migrationDs = HikariDataSource(hc)try { DbMigration(fwCfg, migrationDs).migrate().onFailure { throw it }} finally { migrationDs.close()}
// CORRECTHikariDataSource(hc).use { migrationDs -> DbMigration(fwCfg, migrationDs).migrate().onFailure { throw it }}When use does not fit
Section titled “When use does not fit”use is for resources whose lifetime ends with the current block. It does
not fit when the resource’s lifetime is conditional — for example, a pool
that must outlive the function on the happy path (because it backs a value you
return to the caller) but must be closed on the failure path. In that case
keep an explicit try { ... } catch { resource.close(); throw }:
val runtimePool = HikariDataSource(hc).also { allCreatedPools.add(it) }val db = Database.connect(runtimePool)try { HikariDataSource(hc).use { migrationDs -> DbMigration(fwCfg, migrationDs).migrate().onFailure { throw it } }} catch (t: Throwable) { // runtimePool's lifetime is conditional — close it only on the failure // path, since `db` (and therefore the pool) is returned to the caller on // success. allCreatedPools.remove(runtimePool) runtimePool.close() throw t}return dbAnti-pattern: surrogate close on the wrong type
Section titled “Anti-pattern: surrogate close on the wrong type”Calling dataSource.connection.close() on a javax.sql.DataSource returns
one connection to the pool — it does not close the pool. If the intent
is to release the pool, hold a typed handle to the pool implementation (e.g.,
HikariDataSource) and wrap it in use { ... }.
Dependency Injection
Section titled “Dependency Injection”- Inject dependencies, do not construct them internally. Classes should receive their dependencies (services, clients, configuration) as constructor parameters. The wiring point (Module.kt) creates all dependencies and passes them in. Classes should not create their own service instances, SDK clients, or other infrastructure objects.
- When a class needs access to an internal component of a dependency (e.g., an
S3AsyncClientowned by anS3AssetService), the dependency should expose it as a public property rather than having the consumer construct a separate instance.
Related
Section titled “Related”- Module Wiring Entry Point — where injected collaborators are constructed.
- Errors and Exceptions — failures during resource acquisition.
- Kotlin Coding Standards — index.
Copyright: © Arda Systems 2025-2026, All rights reserved