Migrating from smbj or jcifs-ng
Why Migrate?
smb-kotlin offers several advantages over smbj and jcifs-ng:
Modern Kotlin API — Designed from the ground up for Kotlin with idiomatic patterns, extension functions, and null safety. No Java boilerplate.
Coroutine-based async — All operations are suspend functions. No blocking threads while waiting for network I/O. Integrates naturally with Android viewModelScope and structured concurrency.
SMB3 encryption — Built-in support for AES-128-CCM encryption with a single configuration flag.
Android support — Tested on Android with support for custom socket factories (BlackBerry Dynamics, MDM containers).
Streaming I/O — Built on Okio for efficient streaming reads and writes without loading entire files into memory.
Active maintenance — Actively developed and maintained, with regular updates for protocol compatibility and security fixes.
Key Differences
| Feature | smbj / jcifs-ng | smb-kotlin |
|---|---|---|
| API style | Blocking Java API | Suspend functions (coroutines) |
| Connection model | Multi-step (client, connection, session, share) | Single call: connectShare() |
| File I/O | InputStream / OutputStream | Okio Source / Sink with lambdas |
| Error handling | Checked exceptions (Java) | Kotlin exceptions with SMB status codes |
| Thread safety | Manual synchronization | Coroutine-safe by design |
| Directory listing | Returns List (blocks until complete) | Returns Flow (streams results) |
| Android | Limited / unofficial | First-class support |
Migration from smbj
Connecting to a Share
smbj:
// smbj — 4 objects to manage
SMBClient client = new SMBClient();
Connection connection = client.connect("fileserver.local");
AuthenticationContext auth = new AuthenticationContext("user", "pass".toCharArray(), "DOMAIN");
Session session = connection.authenticate(auth);
DiskShare share = (DiskShare) session.connectShare("documents"); smb-kotlin:
// smb-kotlin — single call
val client = SmbClient()
val share = client.connectShare(
host = "fileserver.local",
shareName = "documents",
username = "user",
password = "pass",
domain = "DOMAIN"
) Reading a File
smbj:
// smbj
File file = share.openFile("report.pdf",
EnumSet.of(AccessMask.GENERIC_READ), null, SMB2ShareAccess.ALL,
SMB2CreateDisposition.FILE_OPEN, null);
InputStream in = file.getInputStream();
byte[] data = in.readAllBytes();
in.close();
file.close(); smb-kotlin:
// smb-kotlin
share.readFile("report.pdf") { source ->
val data = source.readByteArray()
} Writing a File
smbj:
// smbj
File file = share.openFile("output.txt",
EnumSet.of(AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL,
SMB2CreateDisposition.FILE_OVERWRITE_IF, null);
OutputStream out = file.getOutputStream();
out.write(data);
out.close();
file.close(); smb-kotlin:
// smb-kotlin
share.writeFile("output.txt") { sink ->
sink.write(data)
} Listing a Directory
smbj:
// smbj — blocks until all entries are loaded
List<FileIdBothDirectoryInformation> entries = share.list("projects");
for (FileIdBothDirectoryInformation entry : entries) {
System.out.println(entry.getFileName());
} smb-kotlin:
// smb-kotlin — streams entries as they arrive
share.listDirectory("projects").collect { entry ->
println(entry.name)
} Migration from jcifs-ng
Connecting and Browsing
jcifs-ng:
// jcifs-ng — URL-based API
CIFSContext ctx = new BaseContext(new PropertyConfiguration(new Properties()));
NtlmPasswordAuthenticator auth = new NtlmPasswordAuthenticator("DOMAIN", "user", "pass");
CIFSContext authCtx = ctx.withCredentials(auth);
SmbFile dir = new SmbFile("smb://fileserver.local/documents/", authCtx); smb-kotlin:
// smb-kotlin
val client = SmbClient()
val share = client.connectShare(
host = "fileserver.local",
shareName = "documents",
username = "user",
password = "pass",
domain = "DOMAIN"
) Reading a File
jcifs-ng:
// jcifs-ng
SmbFile file = new SmbFile("smb://fileserver.local/documents/report.pdf", authCtx);
SmbFileInputStream in = new SmbFileInputStream(file);
byte[] data = in.readAllBytes();
in.close(); smb-kotlin:
// smb-kotlin
share.readFile("report.pdf") { source ->
val data = source.readByteArray()
} Listing a Directory
jcifs-ng:
// jcifs-ng — blocks, returns everything at once
SmbFile dir = new SmbFile("smb://fileserver.local/documents/projects/", authCtx);
SmbFile[] files = dir.listFiles();
for (SmbFile f : files) {
System.out.println(f.getName());
} smb-kotlin:
// smb-kotlin — streams results
share.listDirectory("projects").collect { entry ->
println(entry.name)
} Dependency Changes
Remove your existing SMB library dependency and add smb-kotlin:
Gradle (Kotlin DSL)
// Remove one of these:
// implementation("com.hierynomus:smbj:0.13.0")
// implementation("eu.agno3.jcifs:jcifs-ng:2.1.10")
// Add smb-kotlin:
implementation("com.ctreesoft:smb-kotlin:1.3.0") Gradle (Groovy)
// Remove one of these:
// implementation 'com.hierynomus:smbj:0.13.0'
// implementation 'eu.agno3.jcifs:jcifs-ng:2.1.10'
// Add smb-kotlin:
implementation 'com.ctreesoft:smb-kotlin:1.3.0' Maven
<!-- Remove smbj or jcifs-ng dependency, then add: -->
<dependency>
<groupId>com.ctreesoft</groupId>
<artifactId>smb-kotlin</artifactId>
<version>1.3.0</version>
</dependency>