Home โ€บ Java โ€บ Testing Spring REST APIs with curl: A Senior Dev's Guide
Beginner 4 min · July 14, 2026

Testing Spring REST APIs with curl: A Senior Dev's Guide

Learn how to test and debug Spring Boot REST APIs using curl.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of REST APIs and HTTP methods (GET, POST, PUT, DELETE).
  • A running Spring Boot application with REST endpoints (can be local or remote).
  • curl installed on your system (pre-installed on macOS/Linux, download for Windows).
  • Familiarity with JSON format and basic shell commands.
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use curl -v for verbose output to see headers and status codes.
  • Test POST/PUT with -H "Content-Type: application/json" -d '{"key":"value"}'.
  • Debug authentication with -u user:pass or -H "Authorization: Bearer ".
  • Use -w "\n%{http_code}\n" to extract HTTP status codes.
  • Combine with jq to parse JSON responses in scripts.
โœฆ Definition~90s read
What is Testing and Debugging Spring REST APIs with curl?

Curl is a command-line tool for transferring data with URLs, essential for testing and debugging Spring REST APIs by sending raw HTTP requests and inspecting responses.

โ˜…
Think of curl as a phone you use to call your API.
Plain-English First

Think of curl as a phone you use to call your API. You dial the number (URL), say the right words (HTTP method and headers), and listen to the response. It's the simplest way to check if your API is working without needing a fancy app.

I've been building Spring applications since 2008, and if there's one tool I can't live without, it's curl. Not Postman, not Insomnia โ€” curl. Why? Because when you're SSH'd into a production server at 2 AM trying to figure out why a payment endpoint is returning 500, you don't have a GUI. You have a terminal. And curl is your best friend.

This article isn't a rehash of the curl man page. It's a battle-tested guide to testing and debugging Spring REST APIs using curl. I'll show you exactly what commands I run, what I look for in the output, and how to avoid the pitfalls that have burned me (and probably burned you too).

We'll cover everything from basic GET requests to debugging complex scenarios like multipart uploads, authentication failures, and slow endpoints. Along the way, I'll share war stories from production incidents where curl saved the day.

By the end, you'll not only know how to use curl effectively, but you'll also understand the nuances of HTTP that many developers gloss over. Let's dive in.

Why curl is Essential for Spring REST API Testing

When I started with Spring, I used browser-based tools like Postman. They're great for exploration, but they hide too much. Curl gives you raw HTTP. You see exactly what goes over the wire.

Here's a scenario: your Spring Boot app returns a 400 Bad Request for a POST endpoint. Postman might show you a pretty JSON error, but curl with -v shows you the exact request headers, response headers, and body. You can spot missing Content-Type headers, malformed JSON, or unexpected redirects.

Curl also integrates seamlessly into scripts and CI/CD pipelines. You can write a shell script that curls your health endpoint every minute and alerts if it returns non-200. You can't do that with a GUI tool.

Finally, curl is everywhere. Every Linux server, every Docker container, every CI runner has curl. When you're debugging in production, you don't have the luxury of installing Postman. You have curl.

Key Takeaway: Invest time in learning curl. It pays dividends when things go wrong.

basic-curl-commands.shJAVA
1
2
3
4
5
6
7
8
9
10
11
# Basic GET request
curl http://localhost:8080/api/items

# GET with verbose output
curl -v http://localhost:8080/api/items

# GET with custom header
curl -H "Accept: application/json" http://localhost:8080/api/items

# GET with query parameters
curl "http://localhost:8080/api/items?page=0&size=10"
Output
HTTP/1.1 200 OK
Content-Type: application/json
...
[{"id":1,"name":"Item1"},...]
๐Ÿ’กAlways Use -v First
๐Ÿ“Š Production Insight
In production, never use -v on endpoints that return sensitive data in headers (like tokens). Instead, use -s -o /dev/null -w "%{http_code}" to just check the status code.
๐ŸŽฏ Key Takeaway
Curl gives you raw HTTP visibility that GUI tools hide. Use it as your primary debugging tool.

Testing GET Requests and Query Parameters

GET requests are straightforward, but there are nuances. Spring Boot uses @RequestParam to bind query parameters. If your endpoint expects a parameter and you don't provide it, you get a 400.

``java @GetMapping("/items") public List<Item> getItems(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { return service.getItems(page, size); } ``

If you call curl http://localhost:8080/items, it works because of default values. But if the parameter is required, you'll get an error. Use curl to test both cases.

Pro tip: Use --data-urlencode for complex query parameters. For example, if you need to pass a date range: curl "http://localhost:8080/items?startDate=2024-01-01&endDate=2024-12-31". This works for simple values, but if your parameter contains special characters, use --get with --data-urlencode.

Common Mistake: Forgetting to URL-encode special characters. If your query parameter has spaces or ampersands, curl's --data-urlencode handles it.

Key Takeaway: Test both with and without required parameters to ensure your endpoint handles missing parameters gracefully.

curl-get-examples.shJAVA
1
2
3
4
5
6
7
8
9
10
11
# GET with required parameter
curl -v "http://localhost:8080/items?category=electronics"

# GET with multiple parameters
curl -v "http://localhost:8080/items?page=0&size=5&sort=name,asc"

# GET with URL-encoded parameter
curl -v --get --data-urlencode "name=John Doe" http://localhost:8080/items/search

# GET with path variable
curl -v http://localhost:8080/items/123
Output
HTTP/1.1 200 OK
...
[{"id":123,"name":"Laptop","category":"electronics"}]
โš  Beware of Trailing Slashes
๐Ÿ“Š Production Insight
I once spent an hour debugging a 404 because the production load balancer stripped trailing slashes. Add a RedirectView or configure Spring to ignore trailing slashes using configurePathMatch.
๐ŸŽฏ Key Takeaway
Use --data-urlencode for complex query parameters and always test with trailing slashes.

Testing POST and PUT Requests with JSON Payloads

POST and PUT requests are where most issues occur. The most common mistake is forgetting to set the Content-Type header. Without it, Spring doesn't know how to deserialize the body.

``bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"name":"New Item","price":19.99}' \ http://localhost:8080/api/items ``

Notice the single quotes around the JSON. This prevents shell expansion. If you're on Windows, use double quotes and escape inner double quotes.

Pro tip: For complex JSON, store it in a file and use -d @file.json. This avoids escaping issues and makes it reusable.

Common Mistake: Sending JSON with trailing commas or comments. Spring's Jackson parser is strict. Validate your JSON with jq . before sending.

What about PUT vs POST? PUT is for full updates, POST for creation. But many APIs use POST for both. The curl command is identical except for -X PUT. Always check the HTTP method your controller expects.

Key Takeaway: Always set Content-Type: application/json and validate your JSON before sending.

curl-post-example.shJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# POST with inline JSON
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name":"Laptop","price":999.99}' \
  http://localhost:8080/api/items

# POST with JSON file
curl -X POST \
  -H "Content-Type: application/json" \
  -d @new-item.json \
  http://localhost:8080/api/items

# PUT for update
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"name":"Gaming Laptop","price":1299.99}' \
  http://localhost:8080/api/items/123
Output
HTTP/1.1 201 Created
Location: http://localhost:8080/api/items/456
Content-Type: application/json
{"id":456,"name":"Laptop","price":999.99}
๐Ÿ”ฅUse -i to See Response Headers
๐Ÿ“Š Production Insight
In one incident, a developer used -d "{\"name\":\"test\"}" (double quotes with escaped quotes) and the shell mangled the JSON. The API returned 400 and the team spent hours debugging. Use single quotes or a file.
๐ŸŽฏ Key Takeaway
Always set Content-Type and use -d @file for complex payloads. Validate JSON with jq.

Debugging Authentication and Authorization

Authentication issues are the most common cause of 401 errors. Spring Security can be configured in many ways: Basic Auth, JWT tokens, OAuth2, etc. Curl handles them all.

Basic Auth: ``bash curl -u username:password http://localhost:8080/api/secure ` This sends an Authorization: Basic <base64> header. Use -v` to see the header.

Bearer Token (JWT): ``bash curl -H "Authorization: Bearer <token>" http://localhost:8080/api/secure ` Get the token from your login endpoint first: `bash TOKEN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"secret"}' http://localhost:8080/api/login | jq -r '.token') curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/secure ``

Common Mistake: Including the word 'Bearer' incorrectly. The format is Bearer <token> with a space. Also, ensure the token hasn't expired.

Pro tip: Use -w "%{http_code}" to check the status code without the response body. A 401 means unauthorized, 403 means forbidden (authenticated but no permission).

Key Takeaway: Test authentication flows end-to-end: login, get token, access secured endpoint, and verify token expiry.

curl-auth-examples.shJAVA
1
2
3
4
5
6
7
8
9
10
# Basic Auth
curl -v -u admin:secret http://localhost:8080/api/secure

# Bearer Token (JWT)
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"secret"}' http://localhost:8080/api/login | jq -r '.accessToken')
echo "Token: $TOKEN"
curl -v -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/secure

# Check token expiry
curl -v -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/secure/validate
Output
HTTP/1.1 200 OK
...
{"username":"admin","roles":["ROLE_USER"]}
โš  Never Log Tokens
๐Ÿ“Š Production Insight
A client once had a 401 error because their JWT token was being truncated by a proxy. Using curl -v revealed the Authorization header was incomplete. Always verify the exact header sent.
๐ŸŽฏ Key Takeaway
Test the full auth flow with curl: login, token extraction, and secured endpoint access.

What the Official Docs Won't Tell You

The Spring Boot documentation is excellent, but it doesn't cover the gritty details of debugging with curl. Here are the gotchas I've learned the hard way:

1. Content-Type matters even for GET requests. Spring's @RequestMapping can consume specific content types. If you set consumes = "application/json" on a GET endpoint, you must send Accept: application/json header. Otherwise, you get a 406 Not Acceptable.

2. Multipart uploads need special handling. Use -F for file uploads. Curl automatically sets the Content-Type to multipart/form-data. But if you manually set the header, it breaks. Let curl handle it.

3. The -d flag implies POST. If you use -d without -X POST, curl defaults to POST. But if you combine -d with -X GET, curl ignores the body silently. Always specify the method explicitly.

4. Redirects are not followed by default. Spring Boot may redirect HTTP to HTTPS. Use -L to follow redirects. Without it, you'll see a 302 and wonder why.

5. Timing breakdowns are hidden. Use -w " " with format variables to see DNS lookup, connect, start transfer, and total time. This is invaluable for performance debugging.

Key Takeaway: The official docs assume you know HTTP basics. Curl exposes the raw protocol, so understanding these nuances is critical.

curl-advanced-tricks.shJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Follow redirects
curl -L http://localhost:8080/api/items

# Timing breakdown
curl -w "\nDNS: %{time_namelookup}s\nConnect: %{time_connect}s\nStart Transfer: %{time_starttransfer}s\nTotal: %{time_total}s\n" -o /dev/null -s http://localhost:8080/api/items

# Test with custom headers for content negotiation
curl -H "Accept: application/xml" http://localhost:8080/api/items

# Multipart file upload
curl -X POST -F "file=@report.pdf" -F "description=Monthly report" http://localhost:8080/api/upload

# Send raw request body from stdin
echo '{"name":"test"}' | curl -X POST -H "Content-Type: application/json" -d @- http://localhost:8080/api/items
Output
DNS: 0.001s
Connect: 0.010s
Start Transfer: 0.050s
Total: 0.061s
๐Ÿ’กUse -w for Performance Debugging
๐Ÿ“Š Production Insight
I once debugged a slow API that turned out to be a DNS issue. The timing breakdown showed time_namelookup was 5 seconds. The fix was to add the hostname to /etc/hosts. Curl's timing saved hours.
๐ŸŽฏ Key Takeaway
Master curl's hidden features: redirects, timing, multipart, and content negotiation.

Automating Tests with Curl Scripts

Curl isn't just for ad-hoc debugging. You can build a suite of smoke tests using shell scripts. This is especially useful for CI/CD pipelines where you want to quickly verify that your Spring Boot app is healthy.

```bash #!/bin/bash BASE_URL="http://localhost:8080/api" errors=0

# Test GET endpoint echo "Testing GET /items..." response=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/items) if [ "$response" -ne 200 ]; then echo "FAIL: Expected 200, got $response" errors=$((errors+1)) fi

# Test POST endpoint echo "Testing POST /items..." response=$(curl -s -o /dev/null -w "%{http_code}" -X POST -H "Content-Type: application/json" -d '{"name":"test"}' $BASE_URL/items) if [ "$response" -ne 201 ]; then echo "FAIL: Expected 201, got $response" errors=$((errors+1)) fi

exit $errors ```

Pro tip: Use jq to validate response bodies. For example, check that a field exists or matches an expected value.

Common Mistake: Not waiting for the app to be ready. Use a loop with a timeout to retry until the endpoint returns 200.

Key Takeaway: Automate curl tests in your CI pipeline to catch regressions early.

smoke-test.shJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
BASE_URL="http://localhost:8080/api"
MAX_RETRIES=10
RETRY_INTERVAL=2

# Wait for app to be ready
for i in $(seq 1 $MAX_RETRIES); do
    curl -s -o /dev/null $BASE_URL/health && break
    echo "Waiting for app... ($i/$MAX_RETRIES)"
    sleep $RETRY_INTERVAL
done

# Run tests
echo "Running smoke tests..."

# Test health endpoint
status=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/health)
[ "$status" -eq 200 ] || { echo "Health check failed: $status"; exit 1; }

# Test create item
response=$(curl -s -X POST -H "Content-Type: application/json" -d '{"name":"TestItem"}' $BASE_URL/items)
itemId=$(echo $response | jq -r '.id')
[ "$itemId" != "null" ] || { echo "Failed to create item"; exit 1; }

# Test get item by id
status=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/items/$itemId)
[ "$status" -eq 200 ] || { echo "Get item failed: $status"; exit 1; }

echo "All tests passed!"
Output
Waiting for app... (1/10)
Waiting for app... (2/10)
Running smoke tests...
All tests passed!
๐Ÿ”ฅIntegrate with CI/CD
๐Ÿ“Š Production Insight
We once had a deployment where the app started but the database migration failed silently. The health endpoint returned 200 because it didn't check the database. Our curl smoke test caught it because it tried to create an item and got a 500. Always test real functionality, not just health endpoints.
๐ŸŽฏ Key Takeaway
Automate curl smoke tests to catch deployment failures fast.
● Production incidentPOST-MORTEMseverity: high

The Case of the Vanishing Payment Confirmation

Symptom
Users received a 'Payment Successful' message but the payment was never processed. The frontend showed a green checkmark, but the backend logs showed no record of the transaction.
Assumption
The developer assumed the API was working because curl returned HTTP 200 and a JSON response with "status":"success".
Root cause
The Spring Boot controller had a typo in the response body: it returned {"status":"success"} even when the service threw an exception, because the controller caught a generic Exception and returned a hardcoded success response instead of propagating the error. The actual payment processing failed due to a null pointer in a downstream service.
Fix
Removed the catch-all in the controller. Added proper exception handling with @ExceptionHandler and @ControllerAdvice. The API now returns 500 with a meaningful error message when payment fails.
Key lesson
  • Never catch generic exceptions in controllers and return success responses. Let Spring's error handling mechanisms work.
  • Always validate the response body matches the HTTP status code. A 200 with an error body is a red flag.
  • Use curl to test both happy and unhappy paths. Don't just check the status code; inspect the response body.
  • Add structured logging to trace the actual execution path through your service layer.
  • Implement health checks and monitoring that verify end-to-end functionality, not just HTTP response codes.
Production debug guideSymptom to Action5 entries
Symptom · 01
API returns 500 Internal Server Error
Fix
Run curl -v <endpoint> to see the response headers and body. Check if the error message includes a stack trace (disable server.error.include-stacktrace in production). Look for X-Application-Error custom headers. If no body, enable debug logging for the controller package.
Symptom · 02
API returns 401 Unauthorized but you have valid credentials
Fix
Use curl -v -u user:pass or -H "Authorization: Bearer <token>" and inspect the WWW-Authenticate response header. Ensure the token isn't expired. Test with a simple curl without auth to confirm the endpoint requires auth.
Symptom · 03
POST request returns 400 Bad Request
Fix
Verify the request body is valid JSON. Use curl -H "Content-Type: application/json" -d '{"key":"value"}' and check for JSON parsing errors in the response. Ensure all required fields are present and correctly typed.
Symptom · 04
API returns 404 Not Found for an existing endpoint
Fix
Check the URL path and HTTP method. Use curl -X POST vs curl -X GET. Verify trailing slashes. Use curl -v to see the full request URL. Check Spring's RequestMapping for path conflicts.
Symptom · 05
API response is slow (>2 seconds)
Fix
Use curl -w "@%{time_total}\n" -o /dev/null -s to measure total time. Add -w "\n" for timing breakdowns. Check if the endpoint is hitting a database or external service. Use curl --limit-rate 100k to simulate slow network.
★ Quick Debug Cheat SheetCommon curl commands for Spring REST API debugging
Check response status and headers
Immediate action
Run `curl -v`
Commands
curl -v http://localhost:8080/api/endpoint
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/endpoint
Fix now
Use -v to see request/response details
Test POST with JSON body+
Immediate action
Run curl with `-H` and `-d`
Commands
curl -X POST -H "Content-Type: application/json" -d '{"name":"test"}' http://localhost:8080/api/items
curl -X POST -H "Content-Type: application/json" -d @payload.json http://localhost:8080/api/items
Fix now
Validate JSON with jq . before sending
Test authentication+
Immediate action
Use `-u` or `-H` for token
Commands
curl -u username:password http://localhost:8080/api/secure
curl -H "Authorization: Bearer <token>" http://localhost:8080/api/secure
Fix now
Check token expiry and format
Measure response time+
Immediate action
Use `-w` option
Commands
curl -w "\nTotal time: %{time_total}s\n" -o /dev/null -s http://localhost:8080/api/endpoint
curl -w "@timing-format.txt" -o /dev/null -s http://localhost:8080/api/endpoint
Fix now
Compare with baseline; look for slow external calls
Test file upload+
Immediate action
Use `-F` option
Commands
curl -X POST -F "file=@document.pdf" http://localhost:8080/api/upload
curl -X POST -F "file=@document.pdf;type=application/pdf" http://localhost:8080/api/upload
Fix now
Check multipart configuration in Spring Boot
ToolBest ForProsCons
curlQuick debugging, automation, productionLightweight, ubiquitous, scriptableCommand-line only, no GUI
PostmanExploration, collaborationGUI, collections, environment variablesHeavy, not available on servers
REST AssuredIntegration testing in JavaFluent API, integrates with JUnitRequires Java knowledge, slower setup
Spring WebClientReactive API testingPart of Spring, async supportMore complex for simple tests
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
basic-curl-commands.shcurl http://localhost:8080/api/itemsWhy curl is Essential for Spring REST API Testing
curl-get-examples.shcurl -v "http://localhost:8080/items?category=electronics"Testing GET Requests and Query Parameters
curl-post-example.shcurl -X POST \Testing POST and PUT Requests with JSON Payloads
curl-auth-examples.shcurl -v -u admin:secret http://localhost:8080/api/secureDebugging Authentication and Authorization
curl-advanced-tricks.shcurl -L http://localhost:8080/api/itemsWhat the Official Docs Won't Tell You
smoke-test.shBASE_URL="http://localhost:8080/api"Automating Tests with Curl Scripts

Key takeaways

1
Curl is the most reliable tool for debugging Spring REST APIs in any environment, especially production.
2
Always use -v for verbose output to see the exact request and response headers.
3
Validate JSON payloads with jq before sending to avoid parsing errors.
4
Automate curl smoke tests in CI/CD pipelines to catch deployment failures early.
5
Master curl's timing and formatting options to diagnose performance issues.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How would you use curl to test a Spring Boot REST API that requires a JW...
Q02SENIOR
Explain how to debug a 500 Internal Server Error using curl. What specif...
Q03JUNIOR
How would you test a Spring Boot endpoint that supports content negotiat...
Q01 of 03SENIOR

How would you use curl to test a Spring Boot REST API that requires a JWT token?

ANSWER
First, obtain the token by calling the login endpoint: TOKEN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"pass"}' http://localhost:8080/api/login | jq -r '.token'). Then, use the token in the Authorization header: curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/secure. To test token expiry, wait and re-request.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I test a Spring Boot POST endpoint with curl?
02
Why does my curl request return 400 Bad Request?
03
How can I measure API response time with curl?
04
How do I test file upload endpoints in Spring Boot with curl?
05
How do I handle authentication with curl for Spring Security?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Spring Boot. Mark it forged?

4 min read · try the examples if you haven't

Previous
Getting and Verifying JSON Response Data with REST-assured
111 / 121 · Spring Boot
Next
Spring RestTemplate Error Handling: ResponseErrorHandler and Custom Strategies