Skip to content

Go

invalid character '<' looking for beginning of value

encoding/json expected the start of a JSON value and found the character in quotes instead; < means the input is HTML or XML.

Updated · 2 min read

console
Go encoding/jsoninvalid character '<' looking for beginning of value
Go, NaN or a Not Found bodyinvalid character 'N' looking for beginning of value
Go 1.26 and older, UTF-8 BOMinvalid character 'ï' looking for beginning of value
Go 1.27 and newer, UTF-8 BOMinvalid character '\ufeff' looking for beginning of value

Find it in your JSON

Paste the text that failed. The checker points at the exact spot, and Repair fixes most syntax mistakes.

Input · JSON

Invalid JSON

Line 1, column 1

Unexpected '<': this looks like HTML or XML, not JSON.

1<?xml version="1.0" encoding="UTF-8"?> ^2<Error>

Fix: A server often sends an HTML error or login page where JSON was expected. Check the URL, the status code and the Content-Type header.

Why this happens
Invalid JSONRuns in your browser

Load from a URL

Your browser fetches it directly, so the server must allow cross-origin requests (CORS).

Why Go says invalid character ‘<’ looking for beginning of value

json.Unmarshal and json.Decoder return invalid character '<' looking for beginning of value when a value should start and the byte there is not {, [, ", a digit, -, t, f or n. The quoted character tells you what you received:

  • '<': HTML or XML. An error or login page, a proxy’s 502 page, or an XML error such as the expired presigned-URL response from S3 in the checker above.
  • 'N' or 'I': NaN or Infinity, which Python’s json.dumps writes by default, or a plain-text Not Found or Internal Server Error.
  • 'F', 'U' and other capitals: a plain-text error that starts with its status text, the way http.Error(w, http.StatusText(code), code) writes Forbidden or Unauthorized.
  • 'ï' or '\ufeff': a UTF-8 byte order mark at the start of the body.
  • '\'' followed by looking for beginning of object key string: single quotes, often a Python dict written with str().

Two near misses have their own text. An empty body gives unexpected end of JSON input from Unmarshal and io.EOF from a Decoder. Go’s default 404 body, 404 page not found, gives invalid character 'p' after top-level value, because 404 parses as a number first.

How to fix it

Check the response before decoding

func fetchReport(url string) (*Report, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if err != nil {
		return nil, err
	}
	mediaType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if resp.StatusCode != http.StatusOK || mediaType != "application/json" {
		return nil, fmt.Errorf("GET %s: %s, %s: %.200s", url, resp.Status, mediaType, body)
	}

	body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf")) // UTF-8 BOM
	var r Report
	if err := json.Unmarshal(body, &r); err != nil {
		return nil, fmt.Errorf("decode %s: %w", url, err)
	}
	return &r, nil
}

For the sample above, the error now reads 403 Forbidden, application/xml: <?xml version=... instead of a bare syntax error. json.NewDecoder(resp.Body).Decode(&r) works too, but it consumes the body, so keep the status and type checks in front of it.

Find the bad byte

*json.SyntaxError carries Offset, the number of bytes read up to and including the bad one:

var se *json.SyntaxError
if errors.As(err, &se) {
	start := max(0, int(se.Offset)-30)
	end := min(len(body), int(se.Offset)+30)
	log.Printf("byte %d: %q", se.Offset, body[start:end])
}

Or paste the body into the JSON validator, which shows the line and column.

Fix the sender

NaN and Infinity are not JSON. In Python, replace them with None before json.dumps, or pass allow_nan=False so the sender fails instead of the receiver.

Go tools

Terraform, Docker, kubectl and Grafana are written in Go, so this message also shows up in their output when a configured endpoint answers with HTML. Check the URL, the proxy settings and the credentials the tool uses.

Same error in other languages

The full list by language: common JSON errors.

Questions

Did Go 1.27 change this error?

Go 1.27 runs encoding/json on the new v2 implementation, and its release notes warn that error text may differ. This message kept its wording; the byte order mark case changed, as the console above shows. Build with GOEXPERIMENT=nojsonv2 to compare.

Does json.Decoder report trailing garbage?

No. Decode reads one value and stops, so text after it goes unnoticed. Call Decode again and expect io.EOF if the stream must hold exactly one value.

Related errors

All common JSON errors

Esc