The Exact Error
HTTP/1.1 415 Unsupported Media Type
{
"error": "Unsupported Media Type",
"message": "Content type 'text/plain' not supported"
}
Or in frameworks:
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded' not supported
Quick summary: The request body format declared in
Content-Typedoesn't match what the server accepts. SetContent-Type: application/jsonwhen sending JSON, ormultipart/form-datafor file uploads.
Why This Error Happens
1. Missing Content-Type header ā sending a JSON body without declaring Content-Type: application/json
2. Wrong Content-Type for the body ā sending JSON but declaring Content-Type: text/plain or application/x-www-form-urlencoded
3. File upload without multipart ā sending a file with Content-Type: application/json instead of multipart/form-data
4. Charset mismatch ā some servers require application/json; charset=utf-8 not just application/json
Step-by-Step Diagnosis
Step 1 ā Check what Content-Type you're sending
// In fetch:
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // <-- this must match the body format
},
body: JSON.stringify({ name: 'Alice' }),
});
Step 2 ā Check what the server expects
# Send an OPTIONS request to check CORS and accepted types
curl -X OPTIONS https://api.example.com/endpoint -H "Origin: http://localhost:3000" -v
Step 3 ā Inspect the full request in browser DevTools
Open Network tab, click the request, and look at the Request Headers section. Confirm Content-Type is set correctly.
Solutions
Solution 1 ā Set correct Content-Type for JSON
// fetch
const response = await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
// axios ā sets Content-Type automatically for objects
const response = await axios.post('/api/data', data);
Solution 2 ā Set correct Content-Type for form data
// For HTML form data
const formData = new URLSearchParams({ username: 'alice', password: 'pass' });
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData.toString(),
});
Solution 3 ā Set correct Content-Type for file uploads
// Do NOT set Content-Type manually for FormData ā the browser sets it
// automatically with the correct boundary parameter
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'upload');
const response = await fetch('/api/upload', {
method: 'POST',
// No Content-Type header here ā let the browser set it
body: formData,
});
Solution 4 ā Server side: accept additional media types (Express)
// If you control the server and want to accept both formats:
app.use(express.json()); // application/json
app.use(express.urlencoded({ extended: true })); // application/x-www-form-urlencoded
Real-World Examples
curl command with correct Content-Type:
# WRONG:
curl -X POST https://api.example.com/users -d '{"name":"Alice"}'
# RIGHT:
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}'
Quick Reference ā Content-Type by Body Format
| Body format | Correct Content-Type |
|---|---|
| JSON object | application/json |
| HTML form fields | application/x-www-form-urlencoded |
| File upload | multipart/form-data (set by browser) |
| Plain text | text/plain |
| XML | application/xml or text/xml |
| Binary/blob | application/octet-stream |
Prevent This Error in the Future
1. Use a typed HTTP client like axios that sets Content-Type automatically for object bodies.
2. Never manually set Content-Type for FormData ā the browser must add the boundary parameter.
3. Add Content-Type validation in API tests to catch mismatches early.
Use ToolNinja to Debug Faster
The HTTP Request tool lets you build and send requests with full control over headers and body format ā test your Content-Type against real APIs directly in the browser.