ScalikeJDBC: SQL on JDBC without an ORM, and an optional ActiveRecord-style layer
A tidy SQL-based DB access library for Scala developers. This library naturally wraps JDBC APIs and provides you easy-to-use APIs.
At a glance
- What is it?
- ScalikeJDBC is a Scala library that wraps JDBC directly, letting you write plain SQL in string interpolation with type-safe query building, and it ships a separate scalikejdbc-orm module for those who want entity mappers and join-based eager loading.
- Who is it for?
- ScalikeJDBC fits a Scala team that wants to keep its SQL visible and its types checked, and that does not want an ORM deciding the queries for it. It is a poor fit if you want the ActiveRecord-style experience, because the ORM layer is a separate artifact you opt into, and it is a poor fit for a project on Scala 2.12 or older, since the published artifacts target 2.13.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library is, and the two ways to use it
ScalikeJDBC is a tidy SQL-based database access library for Scala developers, and the description says it naturally wraps JDBC APIs and provides easy-to-use APIs. The README's headline is Just Write SQL And Get Things Done.
That framing is the design. Most Scala database access goes through an ORM, and the objection to that is well known: the queries disappear into method chains and the SQL you ship is not the SQL you wrote. ScalikeJDBC keeps the SQL in the source and adds the type safety around it.
There are two distinct products here, and picking the wrong one is the main adoption mistake.
The core library is scalikejdbc, a set of wrappers over JDBC with a connection pool, string interpolation for SQL, and type-safe query building. The second is scalikejdbc-orm, described as an O/R mapper built on top of the core library and drawing significant inspiration from Ruby on Rails' ActiveRecord library. The README calls the core approach the Simple Database Library and the ORM the Rich O/R Mapper.
They are separate artifacts with separate dependency declarations, and the two quick examples in the README look nothing alike, because the ORM example defines case classes with companion objects extending CRUDMapper while the core example works with raw result sets.
The project is Apache-2.0 licensed with a LICENSE.txt, the homepage is scalikejdbc.org, the last push was on 2026-10-15, and the recent releases are 4.3.5 from 2025-08-16, 4.3.4 from 2025-06-12 and 4.3.3 from 2025-05-24. The Maven Central badge points at the 2.13 cross-built artifact, so the published binaries target Scala 2.13.
The core library, from a REPL to a typed query
The core quick example is a script you can paste into a Scala REPL, and it walks through the whole flow.
import scalikejdbc._
// initialize JDBC driver & connection pool
Class.forName("org.h2.Driver")
ConnectionPool.singleton("jdbc:h2:mem:hello", "user", "pass")
// ad-hoc session provider on the REPL
implicit val session: DBSession = AutoSessionThe JDBC driver is loaded by class name, the pool is set up with a URL and credentials, and AutoSession is the implicit session that the query operations pick up. On a REPL an implicit session is the only way the API reads cleanly, and the same implicit is what you would wire in at application startup in production.
Then DDL runs through the same interpolation as everything else, with a comment noting you can run DDL by using #execute as same as JDBC:
sql"""
create table members (
id serial not null primary key,
name varchar(64),
created_at timestamp not null
)
""".execute.apply()And inserts interpolate the variable directly, with no parameter binding visible in the call:
Seq("Alice", "Bob", "Chris") foreach { name =>
sql"insert into members (name, created_at) values (${name}, current_timestamp)".update.apply()
}Reading back comes in two flavours. The first returns everything as a Map, which the comment describes as for now retrieving all data as Map value. The second defines a case class and an extractor, so the row is shaped by the class rather than by a string key:
case class Member(id: Long, name: Option[String], createdAt: ZonedDateTime)
object Member extends SQLSyntaxSupport[Member] {
override val tableName = "members"
def apply(rs: WrappedResultSet) = new Member(
rs.long("id"), rs.stringOpt("name"), rs.zonedDateTime("created_at"))
}
val members: List[Member] = sql"select * from members".map(rs => Member(rs)).list.apply()The SQLSyntaxSupport trait is the hook that gives you the type-safe query builder. The example builds a syntax object from the companion and uses it to construct a select:
val m = Member.syntax("m")
val name = "Alice"
val alice: Option[Member] = withSQL {
select.from(Member as m).where.eq(m.name, name)
}.map(rs => Member(rs)).single.apply()So the library gives you two ways to write the same query, and the choice is between a literal SQL string and a builder that knows the column types. The comment in the example is that the builder is for the type-safe path, and the interpolation for when you want the SQL in front of you.
The ORM layer, and how it avoids the N+1 problem
The ORM example is a different shape entirely, closer to ActiveRecord than to JDBC.
A CRUDMapper gives you findAll, findAllByIds, and where, and the where has two forms. There is a quick way that is less type-safe, using a string key:
val member1: Seq[Member] = Member.where("name" -> "Alice").apply()And there is the type-safe query builder form:
val member2: Seq[Member] = Member.where(sqls.eq(m.name, "Alice")).apply()The comments in the example show the generated SQL for each, which is the useful part: they are the same statement, and the type-safe one quotes the column name from the syntax object rather than from a string.
TimestampsFeature is a mixin that manages the created and updated columns, so the entity does not have to. The other notable feature is the association handling. The README calls out the ability to efficiently handle data associations, effectively eliminating the common N+1 query problem, achieved through the smart use of join queries in resolving associations like belongsTo, hasOne and hasMany or hasManyThrough. Those are processed behind the scenes.
The escape hatch is the #includes method, which does eager loading, that is resolving the main entity and then performing an in-clause query to resolve deep nested associations. The README recommends it for the cases where a join query is not suitable, which is an honest admission that a single join cannot express every association graph.
The module layout, and the build tooling
The repository top level shows how much is in this project, and the module names are the map of the library's capabilities.
The core is scalikejdbc-core, with scalikejdbc-config for configuration, scalikejdbc-library for the dependency-aggregated artifact, and scalikejdbc-test for the test support. The ORM is scalikejdbc-orm. Then there are the macro modules, scalikejdbc-interpolation and scalikejdbc-interpolation-macro, and the syntax support macro, scalikejdbc-syntax-support-macro, which is what makes the string interpolation type-aware. Two code generation modules exist for generating mappers, scalikejdbc-mapper-generator and scalikejdbc-mapper-generator-core, so a case class and its companion can be written for you. There is a joda-time module for the older date API, a streams module, and a CLI module.
The build is sbt, with build.sbt at the root, a project/ directory for the sbt build definition, and scripts/ and tools-support/ directories. Formatting is scalafmt with a .scalafmt.conf, and dependency versions are managed by Scala Steward with a .scala-steward.conf, which is the standard way to keep a multi-module Scala build current.
The two dependency snippets in the README are worth reading together, because the version policy is unusual. Both use a dynamic version, 4.3.+ for the library and 2.2.+ for h2 and 1.5.+ for logback-classic:
libraryDependencies ++= Seq(
"org.scalikejdbc" %% "scalikejdbc" % "4.3.+",
"com.h2database" % "h2" % "2.2.+",
"ch.qos.logback" % "logback-classic" % "1.5.+"
)The %% operator is the Scala version-aware artifact suffix, which is why the artifact is named scalikejdbc_2.13 on Maven Central. A dynamic version like 4.3.+ means your build resolves the newest patch in that line, which is convenient while you are trying the library and something you would pin before a production release.
The Play Framework integration lives in a separate project, play-support, linked from the README, so the Play-specific session and database setup is not part of the core.
Where the trade-offs are
The first limitation is that the ORM layer is a second decision, not a default. The core library gives you SQL and a typed query builder, and the ORM gives you entity mappers at the cost of learning a second API. A team that wants one thing should pick one thing, because the two quick examples in the README share the connection pool and little else.
The second limitation is the string interpolation style. It is powerful and it is the point of the library, but it means the SQL is assembled at runtime rather than prepared, and the type safety comes from the interpolation macro checking the expression types against the SQL, not from the database. A query written as a literal string is not checked at all beyond what the database will accept at runtime.
The third limitation is the version policy in the published examples. The 4.3.+ dynamic version resolves the newest patch release, so two developers on the same day can be on different builds, and a project that ships an application should pin an exact version instead. The h2 and logback versions in the snippet have the same property.
The fourth limitation is the Scala version. The published artifact is the 2.13 cross-build, and the classifiers in the project are 2.13. A team on Scala 2.12 or 3 does not have a matching artifact here.
The fifth is that the ORM layer is a Rails imitation, and the README says so. That is a reasonable design reference, since ActiveRecord's conventions are well understood, but it does mean the association vocabulary, belongsTo, hasOne, hasMany, hasManyThrough, is borrowed rather than designed for the relational model, and the #includes fallback is the escape hatch where the borrowed vocabulary does not fit.
On maintenance, the repository is not archived, the last push was on 2026-10-15, and the 4.3.x release line has been moving through 2025.
Editorial conclusion
ScalikeJDBC fits a Scala team that wants to keep its SQL visible and its types checked, and that does not want an ORM deciding the queries for it. It is a poor fit if you want the ActiveRecord-style experience, because the ORM layer is a separate artifact you opt into, and it is a poor fit for a project on Scala 2.12 or older, since the published artifacts target 2.13. Before adopting, decide whether you want the core library or scalikejdbc-orm, since the two have different APIs and different dependency sets, and read the core quick example first to see whether the string-interpolated SQL style fits how your team writes queries.
Frequently asked questions
What is ScalikeJDBC?
It is a SQL-based database access library for Scala that wraps JDBC APIs and provides easy-to-use APIs, published under the org.scalikejdbc group with 2.13 cross-built artifacts on Maven Central. The core library is scalikejdbc and there is a separate scalikejdbc-orm module inspired by Ruby on Rails' ActiveRecord.
How do I add ScalikeJDBC to an sbt build?
Add the dependency to build.sbt with libraryDependencies ++= Seq("org.scalikejdbc" %% "scalikejdbc" % "4.3.+", "com.h2database" % "h2" % "2.2.+", "ch.qos.logback" % "logback-classic" % "1.5.+"). The %% suffix makes the artifact cross-built for your Scala version.
What is the difference between scalikejdbc and scalikejdbc-orm?
The core library gives you string-interpolated SQL, a connection pool, a DBSession and a type-safe query builder through SQLSyntaxSupport. scalikejdbc-orm is an O/R mapper on top of the core, with CRUDMapper, TimestampsFeature and association handling through join queries, and it is a separate artifact with its own dependency declaration.
How does scalikejdbc-orm avoid the N+1 query problem?
Through the smart use of join queries when resolving associations such as belongsTo, hasOne and hasMany or hasManyThrough, which are processed behind the scenes. Where a join is not suitable, the #includes method does eager loading, resolving the main entity and then performing an in-clause query for the nested associations.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/scalikejdbc-scalikejdbc)Was this useful?