Testing Spring REST APIs with curl: A Senior Dev's Guide
Learn how to test and debug Spring Boot REST APIs using curl.
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
- ✓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.
- Use
curl -vfor 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:passor-H "Authorization: Bearer." - Use
-w "\n%{http_code}\n"to extract HTTP status codes. - Combine with
jqto parse JSON responses in scripts.
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.
-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.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.
For example, consider this controller:
``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.
RedirectView or configure Spring to ignore trailing slashes using configurePathMatch.--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.
Here's the correct way:
``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.
-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.-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 -v revealed the Authorization header was incomplete. Always verify the exact header sent.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.
time_namelookup was 5 seconds. The fix was to add the hostname to /etc/hosts. Curl's timing saved hours.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.
Here's a simple test script:
```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.
The Case of the Vanishing Payment Confirmation
"status":"success".{"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.@ExceptionHandler and @ControllerAdvice. The API now returns 500 with a meaningful error message when payment fails.- 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.
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.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.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.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.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.curl -v http://localhost:8080/api/endpointcurl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/endpoint-v to see request/response details| File | Command / Code | Purpose |
|---|---|---|
| basic-curl-commands.sh | curl http://localhost:8080/api/items | Why curl is Essential for Spring REST API Testing |
| curl-get-examples.sh | curl -v "http://localhost:8080/items?category=electronics" | Testing GET Requests and Query Parameters |
| curl-post-example.sh | curl -X POST \ | Testing POST and PUT Requests with JSON Payloads |
| curl-auth-examples.sh | curl -v -u admin:secret http://localhost:8080/api/secure | Debugging Authentication and Authorization |
| curl-advanced-tricks.sh | curl -L http://localhost:8080/api/items | What the Official Docs Won't Tell You |
| smoke-test.sh | BASE_URL="http://localhost:8080/api" | Automating Tests with Curl Scripts |
Key takeaways
-v for verbose output to see the exact request and response headers.jq before sending to avoid parsing errors.Interview Questions on This Topic
How would you use curl to test a Spring Boot REST API that requires a JWT token?
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.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
That's Spring Boot. Mark it forged?
4 min read · try the examples if you haven't