Question
I have a Go HTTP response body that I read into a byte slice:
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println("StatusCode=" + strconv.Itoa(resp.StatusCode))
return
}
respByte, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("failed to read response data")
return
}
This works, but later I want to use that response data as an io.Reader again. How can I convert a []byte into an io.Reader in Go?
I tried using NewReader and NewWriter, but I was not successful.
Short Answer
By the end of this page, you will understand how to turn a []byte into an io.Reader in Go, when to use bytes.NewReader vs bytes.NewBuffer vs strings.NewReader, and why this is useful when reusing HTTP response data after calling ReadAll. You will also see common mistakes, practical examples, and a small project that ties the idea together.
Concept
A []byte in Go is raw data stored in memory. An io.Reader is not the data itself—it is something that provides data when another function asks for it.
That distinction is important:
[]byte= the actual bytesio.Reader= a stream-like interface for reading bytes
In Go, many APIs accept an io.Reader because it gives them a flexible way to read from many sources:
- files
- network connections
- buffers in memory
- HTTP bodies
- strings and byte slices wrapped as readers
When you call:
respByte, err := ioutil.ReadAll(resp.Body)
you consume the original resp.Body stream and store all data in memory as a []byte.
If you later need to pass that same data to something expecting an io.Reader, you do not "convert" the slice directly. Instead, you wrap the byte slice in a reader type from the bytes package.
The most common choices are:
bytes.NewReader(data)bytes.NewBuffer(data)
Mental Model
Think of []byte as a container full of water and io.Reader as a tap attached to that container.
- The container holds the data.
- The tap lets other code read the data gradually.
A byte slice by itself is just stored data. It does not know how to behave like a stream. bytes.NewReader attaches the tap.
So instead of asking, "How do I change bytes into a reader?" think:
"How do I wrap these bytes in something that can read them?"
That is exactly what bytes.NewReader does.
Syntax and Examples
The core syntax is:
reader := bytes.NewReader(data)
Here, data is a []byte, and reader can be used anywhere an io.Reader is needed.
Basic example
package main
import (
"bytes"
"fmt"
"io"
)
func main() {
data := []byte("hello, Go")
reader := bytes.NewReader(data)
output, err := io.ReadAll(reader)
if err != nil {
panic(err)
}
fmt.Println(string(output))
}
Output:
hello, Go
What happened?
datastores the bytes.bytes.NewReader(data)wraps those bytes in a reader.io.ReadAll(reader)reads from that reader.
Step by Step Execution
Consider this example:
package main
import (
"bytes"
"fmt"
"io"
)
func main() {
data := []byte("ABC")
reader := bytes.NewReader(data)
chunk := make([]byte, 2)
n1, _ := reader.Read(chunk)
fmt.Println(n1, string(chunk[:n1]))
n2, _ := reader.Read(chunk)
fmt.Println(n2, string(chunk[:n2]))
n3, err := reader.Read(chunk)
fmt.Println(n3, err == io.EOF)
}
Step by step
-
data := []byte("ABC")- A byte slice is created in memory.
-
reader := bytes.NewReader(data)- A reader is created that will read from
data.
- A reader is created that will read from
-
chunk := make([]byte, 2)- We prepare a destination slice that can hold 2 bytes at a time.
-
First
Read
Real World Use Cases
Here are common situations where wrapping a []byte as an io.Reader is useful:
Reusing an HTTP response body
You read the body once for logging or validation, then need to pass it to a JSON decoder or another function.
bodyBytes, _ := io.ReadAll(resp.Body)
reader := bytes.NewReader(bodyBytes)
Sending a request body from memory
Many HTTP client methods accept an io.Reader:
payload := []byte(`{"name":"Alice"}`)
resp, err := http.Post(url, "application/json", bytes.NewReader(payload))
Testing functions that read from streams
If your function takes an io.Reader, you can feed test data from a byte slice.
func parse(r io.Reader) error {
// parse data
return nil
}
err := parse(bytes.NewReader([]byte("test input")))
Working with libraries that expect io.Reader
Compression, archiving, hashing, image decoding, and CSV/JSON/XML parsing often accept readers.
Real Codebase Usage
In real Go codebases, developers often use this pattern in a few predictable ways.
1. Read once, reuse many times
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
log.Printf("response: %s", string(bodyBytes))
decoder := json.NewDecoder(bytes.NewReader(bodyBytes))
if err := decoder.Decode(&result); err != nil {
return err
}
This is useful when you want both raw access and structured parsing.
2. Restoring an HTTP body after reading it
In middleware or transport code, developers sometimes read the body and then put it back.
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
resp.Body.Close()
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
This is common if later code still expects resp.Body to be readable.
3. Guard clauses for invalid input
if len(bodyBytes) == 0 {
return errors.New("empty body")
}
reader := bytes.NewReader(bodyBytes)
Small checks like this make stream-related bugs easier to diagnose.
4. Validation before processing
Common Mistakes
Mistake 1: Trying to read resp.Body twice without saving it
Broken example:
body1, _ := io.ReadAll(resp.Body)
body2, _ := io.ReadAll(resp.Body)
Why it fails:
resp.Bodyis a stream.- After the first read, it is exhausted.
- The second read returns no data.
Fix:
body, _ := io.ReadAll(resp.Body)
reader1 := bytes.NewReader(body)
reader2 := bytes.NewReader(body)
Mistake 2: Confusing []byte with io.Reader
Broken example:
func process(r io.Reader) {}
data := []byte("hello")
process(data)
Why it fails:
- A byte slice does not implement
io.Reader.
Fix:
process(bytes.NewReader(data))
Mistake 3: Using the wrong wrapper type without understanding it
and can both act as readers, but they are not identical.
Comparisons
| Option | Input type | Implements io.Reader | Best use case | Notes |
|---|---|---|---|---|
bytes.NewReader(data) | []byte | Yes | Read existing byte slice | Can also seek with Seek |
bytes.NewBuffer(data) | []byte | Yes | Read and buffer operations | Useful if you also want writes |
strings.NewReader(text) | string | Yes | Read from a string | Best when source data is a string |
Cheat Sheet
// Convert []byte to io.Reader
r := bytes.NewReader(data)
// Also works, but more buffer-oriented
r := bytes.NewBuffer(data)
// Convert string to io.Reader
r := strings.NewReader(text)
// Read HTTP body into []byte
bodyBytes, err := io.ReadAll(resp.Body)
// Restore resp.Body after reading it
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
Rules to remember
[]byteis data, not a reader.- Wrap
[]bytewithbytes.NewReaderto get anio.Reader. - Reading from a reader moves its position forward.
- If you need to read again, create a new reader or reset with
Seek. resp.Bodyis usually consumed once.- To replace
resp.Body, useio.NopCloser(...).
Good default choices
- Have
[]byteand needio.Reader? Usebytes.NewReader. - Have
stringand needio.Reader? Use .
FAQ
How do I convert []byte to io.Reader in Go?
Use bytes.NewReader(data) where data is a []byte.
Can I use bytes.NewBuffer instead?
Yes. It also implements io.Reader. For simple read-only access, bytes.NewReader is often clearer.
Why can I not pass a []byte directly where io.Reader is expected?
Because []byte is just raw data and does not implement the Read method required by io.Reader.
How do I reuse resp.Body after calling io.ReadAll?
Read it into bytes, then assign it back like this:
bodyBytes, _ := io.ReadAll(resp.Body)
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
What is the difference between io.Reader and ?
Mini Project
Description
Build a small Go utility that fetches an HTTP response, reads the body into memory, logs the raw text, then restores the body so it can be parsed again. This demonstrates the practical difference between raw bytes and readers, and shows how to reuse consumed stream data safely.
Goal
Create a program that reads an HTTP response body once, converts the bytes back into a readable stream, and decodes the same content again.
Requirements
- Make an HTTP GET request to a public JSON endpoint.
- Read the response body into a
[]byte. - Print the raw response body as text.
- Rebuild the response body so it can be read again.
- Decode the rebuilt body into a Go struct.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.