# smbj: an SMB2 and SMB3 client for Java that stays low level

> smbj is a Java client library for the SMB2 and SMB3 protocols, aimed at developers who need share access from a JVM and are willing to deal with the protocol's own vocabulary. It exposes the wire-level operations, and its optional java.nio FileSystem provider is explicitly incomplete.

**hierynomus/smbj** — Server Message Block (SMB2, SMB3) implementation in Java

- Repository: https://github.com/hierynomus/smbj
- Stars: 827 · Forks: 194
- Language: Java
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/hierynomus-smbj

## A protocol client, not a filesystem in a jar

smbj is an SMB2 and SMB3 client library for Java. The distinction in that sentence matters, because it determines what you get when something goes wrong.

The README is explicit that SMBJ is a low-level SMB client implementation. Most file and directory operations result in a request being sent to the SMB server, and if the server responds with an error, SMBJ passes that error back to your code as an exception carrying the STATUS_ value the server sent. Nothing is smoothed over into a generic IOException at the API level.

The audience is developers building server-side tooling in Java that needs to reach an SMB share: a backup job, an ingestion pipeline, an archival service, a migration script. You are expected to know what an AccessMask is and to read the SMB specification when you hit an error code, because the README states that documenting every possible error condition is out of scope for the project.

The author credits Jeroen van Erp, the group id is com.hierynomus, and the library is published to Maven Central with the group, artifact and version as the coordinate triple. The README also links JavaDocs, a Gradle build badge and a codecov badge, which tells you the project is a normal library with a normal CI setup rather than a collection of scripts.

One practical detail to check before you start: the version attribute in the README text is 0.13.0, while the most recent tags are v0.14.0 and v0.15.0. The README has drifted from the release line.

## Connect, authenticate, connectShare: the shape of every call

The API is a short chain, and every example in the README follows the same shape.

You build an SMBClient, connect to a server, authenticate with an AuthenticationContext, and then connect to a share. The listing example shows the whole thing:

```java
    SMBClient client = new SMBClient();
    try (Connection connection = client.connect("SERVERNAME")) {
        AuthenticationContext ac = new AuthenticationContext("USERNAME", "PASSWORD".toCharArray(), "DOMAIN");
        Session session = connection.authenticate(ac);
        // Connect to Share
        try (DiskShare share = (DiskShare) session.connectShare("SHARENAME")) {
            for (FileIdBothDirectoryInformation f : share.list("FOLDER", "*.TXT")) {
                System.out.println("File : " + f.getFileName());
            }
        }
    }
```

Four things to take from it. The client and the connection are closeable, so the try-with-resources block is not decoration. The password is a char array rather than a String, which keeps it out of the string pool. The share is obtained by casting the session result to DiskShare, which tells you the API is organised around share types rather than a single generic handle. And directory listing is a typed operation returning FileIdBothDirectoryInformation objects, with a folder and a pattern rather than a path.

Deleting a file uses the same chain and replaces the listing loop with a single call on the share:

```java
    SMBClient client = new SMBClient(config);

    try (Connection connection = client.connect("SERVERNAME")) {
        AuthenticationContext ac = new AuthenticationContext("USERNAME", "PASSWORD".toCharArray(), "DOMAIN");
        Session session = connection.authenticate(ac);

        // Connect to Share
        try (DiskShare share = (DiskShare) session.connectShare("SHARENAME")) {
            share.rm("FILE");
        }
    }
```

## Two timeout knobs with very different defaults

Timeout configuration is the part of the API most likely to surprise you, because there are two separate settings and their defaults are nothing alike.

The SmbConfig builder sets them independently:

```java
    SmbConfig config = SmbConfig.builder()
            .withTimeout(120, TimeUnit.SECONDS) // Timeout sets Read, Write, and Transact timeouts (default is 60 seconds)
            .withSoTimeout(180, TimeUnit.SECONDS) // Socket Timeout (default is 0 seconds, blocks forever)
            .build();
```

withTimeout covers read, write and transact operations and defaults to 60 seconds. withSoTimeout is the socket timeout and defaults to 0, which the comment states blocks forever. So on a default configuration, a request that is sent and never answered can hang the calling thread indefinitely, while a request that is answered slowly fails after a minute.

That asymmetry is the design decision to think about. A background job with a thread pool can live with a 60 second default; an interactive service or a request handler cannot. Setting the socket timeout explicitly on a non-zero value is the difference between a bounded failure and a stuck worker.

The config object is then handed to the client constructor, and everything else about the flow is unchanged. The same configuration pattern applies to the other examples in the README, including the file system provider described next.

## The smb:// filesystem provider is partial, and the README lists what is missing

SMBJ registers a new FileSystem provider that handles smb:// URLs, which lets you use the standard java.nio.file APIs against a share. The README is careful about its status: this is not 100% implemented as yet, although it works for basic usage, and it then lists the unimplemented features.

The list is short and worth reading before you design around this route. SMBException to IOException translation is not done. WatchService is not supported. File attributes are not implemented. FileStores is not implemented. UserPrincipalLookupService is not implemented.

The absence of exception translation is the sharpest edge. In the low-level API a failure arrives as an SMBException carrying a STATUS_ code. Through the filesystem provider you are in java.nio land, where callers expect IOException subclasses, and that translation is exactly what has not been written. The WatchService gap matters too, since anything that reacts to changes on a share will find nothing to subscribe to.

Setting the provider up looks like this, with credentials passed through an environment map:

```java
    Map<String, Object> env = new HashMap<>();
    // optional, leave blank for anonymous access to the share
    env.put(SmbFileSystemProvider.DOMAIN_PROPERTY, "domain");
    env.put(SmbFileSystemProvider.USERNAME_PROPERTY, "userName");
    env.put(SmbFileSystemProvider.PASSWORD_PROPERTY, "password".toCharArray());
    FileSystems.newFileSystem(URI.create("smb://<hostname>/<sharename>"), env);
```

The empty environment means anonymous access, and the domain, username and password keys are the SmbFileSystemProvider constants rather than free-form strings.

## Writing through the filesystem provider with a working filename

The write pattern the README shows is worth copying whatever API you use, because it solves a real consistency problem rather than showing off the library.

The idea is to write to a temporary sibling file first and move it into place afterwards:

```java
    // Write a file with a temporary name to ensure nobody reads while writing
    Path path = Paths.get(URI.create("smb://<hostname>/<sharename>/aFolder/file.txt"));
    Path workingPath = path.resolveSibling(path.getFileName().toString() + ".working");
    Files.createDirectories(workingPath.getParent());
    try (Writer w = Files.newBufferedWriter(workingPath)) {
        w.write("Hello World");
    }
    Files.move(workingPath, path);
```

The comment in the source is the whole rationale: nobody reads while writing. A consumer watching the share sees either the old file or the complete new one, because the file appears under its final name only after the contents are fully written.

Two details are worth noticing. The working name is derived with resolveSibling from the target path, so it stays in the same directory and the final move is a rename within one directory rather than a copy across the share. And the parent directories are created explicitly with Files.createDirectories before the write, so a missing folder in the share does not turn into a failure at the point where you were trying to write bytes.

That pattern is not specific to this library. Any writer to a share that other processes read needs the same temporary-name discipline.

## STATUS_ACCESS_DENIED and STATUS_SHARING_VIOLATION in practice

The README's FAQ section is the most practically useful part of the documentation, because it deals with the two errors you will hit first.

STATUS_ACCESS_DENIED means the access you asked for, expressed as a set of AccessMask values, is not allowed by the server for the account you authenticated with. The fix is to request less. If you only need to read the contents of a file, ask for FILE_READ_DATA rather than something broader like GENERIC_READ or GENERIC_ALL. The MAXIMUM_ALLOWED value is the other option: ask the server for whatever the access control list actually permits, then query the FileAccessInformation information class to find out which permissions you were granted. The README points at the Microsoft documentation on creating and opening files for the detail.

STATUS_SHARING_VIOLATION means another process already has the file or directory open in a way incompatible with what you are trying to do. The README begins to explain that this could be your own process, and the text is cut off there, so treat the rest as undocumented. The practical handling is the same regardless of who holds the handle: retry with backoff, and treat the file as in use rather than as missing.

More generally, the README is clear that the STATUS_ value in an SMBApiException is the server's own error code passed straight through, and that per-error documentation is out of scope, with the SMB specification and the Microsoft error code table given as the reference.

## Building from source, versioning, and the licence question

There are two ways to get SMBJ according to the README: add a dependency to your project, or build SMBJ yourself. Binary releases are not provided in the repository, but the jar can be downloaded straight from the Maven Central repository using the group id, artifact name and version from the README attributes.

The build system is Gradle. The repository root holds build.gradle, settings.gradle, gradle.properties, a gradle wrapper with gradlew and gradlew.bat, and a mise.toml for toolchain versions, plus a conf directory and the src tree. The continuous integration badge points at a gradle.yml workflow. For contributors there are CONTRIBUTING-style files at the root and a WORKING_AUTH_TODO document, which suggests authentication work is still in progress on the project, and DIRECTORY-LEASES.md records that less conventional topic.

The licence is the one thing the metadata does not settle. The repository records a licence GitHub cannot classify, and the LICENSE_HEADER file at the root is separate from the LICENSE file itself, so a corporate review should read both rather than trust a badge.

On versions, the tags are v0.15.0 and v0.14.0, both dated 2026-08-21, with v0.15.0 following v0.14.0 by under three hours on the same day. The last push was recent, and the repository is not archived. The numbering shows the project is still pre-1.0, so pin an exact version in a dependency file rather than tracking the newest release, and expect the API to move between minor versions.

## Conclusion

Java teams that need to read or write an SMB share from a server-side JVM should adopt smbj, because the low-level API gives direct control over share operations. It is the wrong choice when you want a finished java.nio filesystem, since the smb:// provider still lacks exception translation, WatchService, file attributes and FileStores, and it is the wrong choice for SMB1. Before adopting, check the version you pin against the Maven Central coordinates for com.hierynomus:smbj, and read the LICENSE file, since the repository records a licence GitHub cannot classify.

## FAQ

### How do I list files on an SMB share with smbj?

Connect with SMBClient, authenticate with an AuthenticationContext, connect the session to the share by casting it to DiskShare, and call share.list with a folder and a pattern. The result is an iterable of FileIdBothDirectoryInformation objects.

### What timeout should I set for smbj?

SmbConfig has two separate settings. withTimeout covers read, write and transact operations and defaults to 60 seconds, while withSoTimeout is the socket timeout and defaults to 0, which the README notes blocks forever, so set it explicitly in a service.

### Is the smb:// java.nio FileSystem provider in smbj complete?

No. The README states it is not 100% implemented and lists the gaps: SMBException to IOException translation, WatchService, file attributes, FileStores and UserPrincipalLookupService are not implemented.

### How do I fix a STATUS_ACCESS_DENIED error when opening a file with smbj?

The requested AccessMask values are not permitted for your account, so reduce them to what you need, for example FILE_READ_DATA instead of GENERIC_READ or GENERIC_ALL. Alternatively request MAXIMUM_ALLOWED and query the FileAccessInformation information class to see which permissions the server granted.

## Sources

- [hierynomus/smbj on GitHub](https://github.com/hierynomus/smbj)
- [Issues](https://github.com/hierynomus/smbj/issues)
- [README](https://github.com/hierynomus/smbj/blob/master/README.md)
- [Releases](https://github.com/hierynomus/smbj/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hierynomus-smbj
