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>