A modern I/O library that complements java.io and java.nio to make it much easier to access, store, and process your data.
—
Okio provides extensive JVM-specific extensions for interoperability with Java I/O, NIO, security APIs, and other JVM libraries. These extensions make it easy to integrate Okio with existing Java codebases.
Convert between Java I/O streams and Okio Sources/Sinks.
/**
* Converts an OutputStream to a Sink
* @return Sink that writes to this OutputStream
*/
fun OutputStream.sink(): Sink
/**
* Converts an InputStream to a Source
* @return Source that reads from this InputStream
*/
fun InputStream.source(): Source
/**
* Converts a Socket's OutputStream to a Sink with timeout support
* @return Sink that writes to the socket with timeout handling
*/
fun Socket.sink(): Sink
/**
* Converts a Socket's InputStream to a Source with timeout support
* @return Source that reads from the socket with timeout handling
*/
fun Socket.source(): Source
/**
* Converts a File to a Source for reading
* @return Source that reads from this file
* @throws FileNotFoundException if file doesn't exist
* @throws IOException if an I/O error occurs
*/
@Throws(IOException::class)
fun File.source(): Source
/**
* Converts a File to a Sink for writing
* @param append If true, append to existing file; if false, overwrite
* @return Sink that writes to this file
* @throws IOException if an I/O error occurs
*/
@Throws(IOException::class)
fun File.sink(append: Boolean = false): Sink
/**
* Converts a File to an appending Sink
* @return Sink that appends to this file
* @throws IOException if an I/O error occurs
*/
@Throws(IOException::class)
fun File.appendingSink(): SinkUsage Examples:
import java.io.*
import java.net.Socket
// File I/O
val file = File("/tmp/example.txt")
val source = file.source().buffer()
val content = source.readUtf8()
source.close()
val sink = file.sink().buffer()
sink.writeUtf8("Hello from Okio!")
sink.close()
// Stream conversion
val byteArrayOutputStream = ByteArrayOutputStream()
val sink = byteArrayOutputStream.sink().buffer()
sink.writeUtf8("Data")
sink.close()
val bytes = byteArrayOutputStream.toByteArray()
val byteArrayInputStream = ByteArrayInputStream(bytes)
val source = byteArrayInputStream.source().buffer()
val data = source.readUtf8()
// Socket I/O with timeout
val socket = Socket("example.com", 80)
socket.soTimeout = 5000 // 5 second timeout
val socketSource = socket.source().buffer()
val socketSink = socket.sink().buffer()
socketSink.writeUtf8("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
socketSink.flush()
val response = socketSource.readUtf8Line()
println(response)
socket.close()Work with Java NIO Path and Channel APIs.
/**
* Converts a NIO Path to a Source for reading
* @param options OpenOption instances to control how file is opened
* @return Source that reads from the path
* @throws IOException if an I/O error occurs
*/
@Throws(IOException::class)
fun java.nio.file.Path.source(vararg options: OpenOption): Source
/**
* Converts a NIO Path to a Sink for writing
* @param options OpenOption instances to control how file is opened
* @return Sink that writes to the path
* @throws IOException if an I/O error occurs
*/
@Throws(IOException::class)
fun java.nio.file.Path.sink(vararg options: OpenOption): SinkUsage Examples:
import java.nio.file.Paths
import java.nio.file.StandardOpenOption.*
val nioPath = Paths.get("/tmp/nio-example.txt")
// Write using NIO path
val sink = nioPath.sink(CREATE, WRITE, TRUNCATE_EXISTING).buffer()
sink.writeUtf8("NIO Path example")
sink.close()
// Read using NIO path
val source = nioPath.source(READ).buffer()
val content = source.readUtf8()
source.close()
println(content)
// Append to file
val appendSink = nioPath.sink(CREATE, WRITE, APPEND).buffer()
appendSink.writeUtf8("\nAppended line")
appendSink.close()Integrate with Java security APIs for encryption and authentication.
/**
* Wraps a Sink with cipher encryption
* @param cipher Initialized Cipher instance for encryption
* @return CipherSink that encrypts data before writing
*/
fun Sink.cipherSink(cipher: Cipher): CipherSink
/**
* Wraps a Source with cipher decryption
* @param cipher Initialized Cipher instance for decryption
* @return CipherSource that decrypts data while reading
*/
fun Source.cipherSource(cipher: Cipher): CipherSource
/**
* Wraps a Sink with MAC computation
* @param mac Initialized Mac instance
* @return HashingSink that computes MAC while writing
*/
fun Sink.hashingSink(mac: Mac): HashingSink
/**
* Wraps a Source with MAC computation
* @param mac Initialized Mac instance
* @return HashingSource that computes MAC while reading
*/
fun Source.hashingSource(mac: Mac): HashingSource
/**
* Wraps a Sink with MessageDigest computation
* @param digest MessageDigest instance
* @return HashingSink that computes digest while writing
*/
fun Sink.hashingSink(digest: MessageDigest): HashingSink
/**
* Wraps a Source with MessageDigest computation
* @param digest MessageDigest instance
* @return HashingSource that computes digest while reading
*/
fun Source.hashingSource(digest: MessageDigest): HashingSourceUsage Examples:
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
// AES encryption
val key = SecretKeySpec("MySecretKey12345".toByteArray(), "AES")
val encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
encryptCipher.init(Cipher.ENCRYPT_MODE, key)
val file = File("/tmp/encrypted.dat")
val encryptedSink = file.sink().cipherSink(encryptCipher).buffer()
encryptedSink.writeUtf8("Secret message")
encryptedSink.close()
// AES decryption
val decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
decryptCipher.init(Cipher.DECRYPT_MODE, key)
val decryptedSource = file.source().cipherSource(decryptCipher).buffer()
val decryptedMessage = decryptedSource.readUtf8()
decryptedSource.close()
println("Decrypted: $decryptedMessage")
// HMAC computation
val hmacKey = SecretKeySpec("hmac-secret".toByteArray(), "HmacSHA256")
val mac = Mac.getInstance("HmacSHA256")
mac.init(hmacKey)
val hmacSink = blackholeSink().hashingSink(mac)
hmacSink.write(Buffer().writeUtf8("data to authenticate"), 19)
hmacSink.flush()
println("HMAC: ${hmacSink.hash.hex()}")
// MessageDigest computation
val sha256 = MessageDigest.getInstance("SHA-256")
val digestSink = blackholeSink().hashingSink(sha256)
digestSink.write(Buffer().writeUtf8("data to hash"), 12)
digestSink.flush()
println("SHA-256: ${digestSink.hash.hex()}")Access classpath resources as a FileSystem.
/**
* Creates a FileSystem that reads from classpath resources
* @return FileSystem for accessing classpath resources
*/
fun ClassLoader.asResourceFileSystem(): FileSystem
/**
* CipherSink encrypts data using a Cipher before writing to underlying sink
*/
class CipherSink(
private val sink: BufferedSink,
private val cipher: Cipher
) : Sink {
/**
* The cipher being used for encryption
*/
val cipher: Cipher
}
/**
* CipherSource decrypts data using a Cipher while reading from underlying source
*/
class CipherSource(
private val source: BufferedSource,
private val cipher: Cipher
) : Source {
/**
* The cipher being used for decryption
*/
val cipher: Cipher
}Usage Examples:
// Access classpath resources
val classLoader = Thread.currentThread().contextClassLoader
val resourceFs = classLoader.asResourceFileSystem()
// Read a resource file
val configPath = "config/application.properties".toPath()
if (resourceFs.exists(configPath)) {
val config = resourceFs.read(configPath) {
readUtf8()
}
println("Config: $config")
}
// List resources in a directory
val resourceDir = "static/css".toPath()
resourceFs.listOrNull(resourceDir)?.forEach { path ->
println("Resource: $path")
}Background thread timeout implementation for non-blocking operations.
/**
* Timeout implementation using a background thread
* Provides asynchronous timeout functionality
*/
class AsyncTimeout : Timeout() {
/**
* Enters this timeout, starting the timeout timer
*/
fun enter()
/**
* Exits this timeout, stopping the timeout timer
* @return true if the timeout was triggered, false otherwise
*/
fun exit(): Boolean
/**
* Cancels this timeout, preventing it from firing
*/
fun cancel()
/**
* Wraps a Sink with this timeout
* @param sink Sink to wrap with timeout
* @return Sink that respects this timeout
*/
fun sink(sink: Sink): Sink
/**
* Wraps a Source with this timeout
* @param source Source to wrap with timeout
* @return Source that respects this timeout
*/
fun source(source: Source): Source
/**
* Executes a block with this timeout applied
* @param block Block to execute with timeout
* @return Result of the block execution
* @throws IOException if timeout occurs
*/
@Throws(IOException::class)
fun <T> withTimeout(block: () -> T): T
/**
* Called when timeout occurs (override to customize behavior)
* Default implementation does nothing
*/
protected open fun timedOut()
/**
* Creates an exception for timeout scenarios
* @param cause Optional underlying cause
* @return IOException to throw on timeout
*/
protected open fun newTimeoutException(cause: IOException?): IOException
}Usage Examples:
import java.util.concurrent.TimeUnit
// Create timeout for network operations
val timeout = AsyncTimeout()
timeout.timeout(30, TimeUnit.SECONDS) // 30 second timeout
try {
val result = timeout.withTimeout {
// Simulate long-running network operation
val socket = Socket("slow-server.com", 80)
val source = socket.source().buffer()
val sink = socket.sink().buffer()
sink.writeUtf8("GET / HTTP/1.1\r\nHost: slow-server.com\r\n\r\n")
sink.flush()
val response = source.readUtf8Line()
socket.close()
response
}
println("Response: $result")
} catch (e: IOException) {
println("Operation timed out or failed: ${e.message}")
}
// Custom timeout behavior
class CustomTimeout : AsyncTimeout() {
override fun timedOut() {
println("Custom timeout triggered!")
}
override fun newTimeoutException(cause: IOException?): IOException {
return IOException("Custom timeout exceeded", cause)
}
}Work with compressed data streams.
/**
* Sink that compresses data using Deflater before writing
*/
class DeflaterSink(
private val sink: Sink,
private val deflater: Deflater
) : Sink {
/**
* The deflater used for compression
*/
val deflater: Deflater
}
/**
* Source that decompresses data using Inflater while reading
*/
class InflaterSource(
private val source: Source,
private val inflater: Inflater
) : Source {
/**
* The inflater used for decompression
*/
val inflater: Inflater
/**
* Attempts to read or inflate data
* @param sink Buffer to read into
* @param byteCount Number of bytes to attempt to read
* @return Number of bytes read, or -1 if exhausted
*/
fun readOrInflate(sink: Buffer, byteCount: Long): Long
/**
* Refills the inflater with more compressed data
* @return true if more data was available
*/
fun refill(): Boolean
}
/**
* Creates a pipe for connecting a source and sink with buffering
* @param maxBufferSize Maximum buffer size for the pipe
* @return Pipe instance with source and sink properties
*/
class Pipe(maxBufferSize: Long) {
/**
* Sink for writing data to the pipe
*/
val sink: Sink
/**
* Source for reading data from the pipe
*/
val source: Source
/**
* Folds the pipe buffer into the provided sink
* @param sink Sink to receive buffered data
*/
fun fold(sink: Sink)
/**
* Cancels the pipe, preventing further operations
*/
fun cancel()
}Usage Examples:
import java.util.zip.Deflater
import java.util.zip.Inflater
// Compress data
val originalData = "This is data to compress. ".repeat(100)
val deflater = Deflater(Deflater.BEST_COMPRESSION)
val compressedBuffer = Buffer()
val deflaterSink = DeflaterSink(compressedBuffer, deflater)
val bufferedSink = deflaterSink.buffer()
bufferedSink.writeUtf8(originalData)
bufferedSink.close()
println("Original size: ${originalData.length}")
println("Compressed size: ${compressedBuffer.size}")
// Decompress data
val inflater = Inflater()
val inflaterSource = InflaterSource(compressedBuffer, inflater)
val bufferedSource = inflaterSource.buffer()
val decompressedData = bufferedSource.readUtf8()
bufferedSource.close()
println("Decompressed matches original: ${decompressedData == originalData}")
// Use Pipe for producer-consumer pattern
val pipe = Pipe(8192) // 8KB buffer
// Producer thread
Thread {
val sink = pipe.sink.buffer()
try {
for (i in 1..100) {
sink.writeUtf8("Message $i\n")
Thread.sleep(10) // Simulate work
}
} finally {
sink.close()
}
}.start()
// Consumer thread
Thread {
val source = pipe.source.buffer()
try {
while (!source.exhausted()) {
val message = source.readUtf8Line()
if (message != null) {
println("Received: $message")
}
}
} finally {
source.close()
}
}.start()Install with Tessl CLI
npx tessl i tessl/maven-com-squareup-okio--okio-jvm