After the Hello World in Post 1, it's time for the classic backend bread-and-butter: a full JSON CRUD cycle. In annotation mode each HTTP verb is just a different curl command sitting on its own interface method. We'll build a small user-management demo against the free ReqRes REST API and show four return-type styles: String, a domain object, a collection, and Void.
The curl Commands Behind Each Verb
# Read one resource (returns a JSON object)
curl -X GET https://reqres.in/api/users/2
# Create a resource (sends JSON, returns the created object with a new id)
curl -X POST https://reqres.in/api/users \
-H 'Content-Type: application/json' \
-d '{"name":"Ada","job":"Engineer"}'
# Replace / update a resource
curl -X PUT https://reqres.in/api/users/2 \
-H 'Content-Type: application/json' \
-d '{"name":"Ada Lovelace","job":"Analyst"}'
# Delete a resource (returns an empty body, HTTP 204)
curl -X DELETE https://reqres.in/api/users/2
Every one of these strings is a valid @JCurlCommand value — no request-builder API involved.
The CRUD Interface
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
public interface UserCrudApi {
@JCurlCommand("curl -X GET 'https://reqres.in/api/users/${id}'")
String getUser(JQuickCurlReq request);
@JCurlCommand("curl -X POST 'https://reqres.in/api/users' "
+ "-H 'Content-Type: application/json' "
+ "-d '{\"name\":\"${name}\",\"job\":\"${job}\"}'")
String createUser(JQuickCurlReq request);
@JCurlCommand("curl -X PUT 'https://reqres.in/api/users/${id}' "
+ "-H 'Content-Type: application/json' "
+ "-d '{\"name\":\"${name}\",\"job\":\"${job}\"}'")
String updateUser(JQuickCurlReq request);
@JCurlCommand("curl -X DELETE 'https://reqres.in/api/users/${id}'")
Void deleteUser(JQuickCurlReq request);
}
Observations:
- Method name, HTTP verb, and URL are decoupled — rename methods freely.
- The return type drives conversion: here
StringandVoid(Voiddiscards the body for DELETE). - JSON bodies use the
${...}placeholders from Post 5, socreateUserandupdateUsershare one command shape each.
Driving the Full Cycle
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public class CrudDemo {
public static void main(String[] args) {
UserCrudApi api = JCurlInvoker.createProxy(UserCrudApi.class);
// CREATE
JQuickCurlReq create = new JQuickCurlReq();
create.put("name", "Ada");
create.put("job", "Engineer");
String created = api.createUser(create);
System.out.println("created: " + created); // JSON incl. new id
// READ (needs an id — parse it from the create response in real code)
JQuickCurlReq read = new JQuickCurlReq();
read.put("id", 2);
System.out.println("read: " + api.getUser(read));
// UPDATE
JQuickCurlReq update = new JQuickCurlReq();
update.put("id", 2);
update.put("name", "Ada Lovelace");
update.put("job", "Analyst");
System.out.println("updated: " + api.updateUser(update));
// DELETE
JQuickCurlReq delete = new JQuickCurlReq();
delete.put("id", 2);
api.deleteUser(delete);
System.out.println("deleted OK");
}
}
Expected behavior: ReqRes is a mock API, so responses are deterministic — POST /api/users answers with the payload plus an id, and DELETE returns HTTP 204.
Beyond String: Typed Return Values
Declaring the method return type is all it takes to skip manual JSON parsing. The response converter maps the JSON body onto your class — mirror the response keys with your fields:
public class UserInfo {
private long id;
private String email;
private String name;
private String job;
// getters and setters omitted for brevity
}
public interface TypedCrudApi {
@JCurlCommand("curl -X GET 'https://reqres.in/api/users/${id}'")
UserInfo getUser(JQuickCurlReq request);
@JCurlCommand("curl -X GET 'https://reqres.in/api/users?page=${page}'")
java.util.List<UserInfo> listUsers(JQuickCurlReq request);
}
Other supported return types worth remembering:
| Return type | Result |
|---|---|
String |
Raw response body as text |
byte[] |
Raw body bytes (great for binary endpoints) |
| Your POJO | Body JSON bound to the object |
java.util.List<T> |
JSON array bound to a list of objects |
Void |
Execute and discard the response body |
JResult / JQuickCurlResponseBody
|
Keep the raw response with extra accessors |
PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE
JQuickCurl's parser and executor cover eight methods today: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and TRACE. PATCH follows the same pattern as PUT with a smaller payload:
@JCurlCommand("curl -X PATCH 'https://reqres.in/api/users/${id}' "
+ "-H 'Content-Type: application/json' "
+ "-d '{\"job\":\"${job}\"}'")
String patchUser(JQuickCurlReq request);
HEAD and OPTIONS usually return no meaningful body — return Void — and TRACE is a bodyless diagnostic verb on the executor side.
Summary
CRUD in annotation mode is literally your tested curl commands with the volatile values promoted to ${...} placeholders. Method naming, return-type conversion (String, POJO, List, Void, byte[], …), and header handling are all declarative. This is the pattern the rest of the series builds on — XML configuration, conditionals, uploads, and downloads are just more curl commands wearing different clothes.
Everything here runs against the open-source dromara/jquick-curl. Post 7 shows the same idea, but with API definitions lifted out of Java entirely and into XML.
Top comments (0)