Simplify Java HTTP Calls with UniHttp: A Declarative Framework Guide
This article introduces UniHttp, a declarative HTTP‑client framework for Java that replaces traditional HttpClient/OkHttp code with annotation‑driven interfaces, shows quick‑start setup, explains all supported annotations, lifecycle hooks, custom client configuration, and a real‑world enterprise integration example.
1. Introduction
In many enterprise projects developers still use low‑level HTTP clients such as HttpClient or OkHttp, which leads to scattered, hard‑to‑maintain integration code. UniHttp provides a declarative, Spring‑style way to call third‑party HTTP APIs as if they were local methods.
2. Quick Start
2.1 Add Dependency
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>uniapi-http</artifactId>
<version>0.0.4</version>
</dependency>2.2 Define an Interface
@HttpApi(url = "http://localhost:8080")
interface UserHttpApi {
@GetHttpInterface("/getUser")
BaseRsp<String> getUser(@QueryPar("name") String param, @HeaderPar("userId") Integer id);
@PostHttpInterface("/addUser")
BaseRsp<Add4DTO> addUser(@BodyJsonPar Add4DTO req);
}The framework automatically generates a proxy that sends the appropriate HTTP request when the methods are invoked.
2.3 Generated HTTP Requests
GET http://localhost:8080/getUser?name=param
Header:
userId: id POST http://localhost:8080/addUser
Header:
Content-Type: application/json
Body:
{
"id": 1,
"name": "jay"
}2.4 Package Scanning
@UniAPIScan("com.xxx.demo.api")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}2.5 Use the Proxy
@Service
class UserAppService {
@Autowired
private UserHttpApi userHttpApi;
public void doSomething() {
userHttpApi.getUser("jay", 3);
}
}3. Annotation Reference
UniHttp defines a family of annotations that map method parameters to different parts of an HTTP request.
@QueryPar – puts the value into the URL query string.
@PathPar – substitutes a path variable (e.g., /users/{id}).
@HeaderPar – adds a request header.
@CookiePar – adds a cookie (supports String, Map, or Cookie objects).
@BodyJsonPar – serialises the value as JSON (Content‑Type: application/json).
@BodyFormPar – sends the value as form data (Content‑Type: application/x-www-form-urlencoded).
@BodyMultiPartPar – builds a multipart/form‑data request (supports files).
@BodyBinaryPar – sends raw binary data (Content‑Type: application/octet-stream).
@ComposePar – groups multiple @Par annotations inside a single object to reduce parameter count.
3.1 Core Annotations
@HttpApi – marks an interface as an HTTP API and can specify a custom processor implementation.
@HttpInterface – marks a method with HTTP method, path, headers, etc. Shortcut annotations such as @GetHttpInterface, @PostHttpInterface, @PutHttpInterface, and @DeleteHttpInterface are provided.
3.2 Raw HttpResponse
If you need the full response (status code, headers, cookies) you can return HttpResponse<T> instead of a deserialized body.
3.3 File Download
For download endpoints UniHttp offers HttpBinaryResponse, HttpFileResponse and HttpInputStreamResponse to handle binary data, files on disk, or streams respectively.
3.4 HttpApiProcessor Lifecycle Hooks
The HttpApiProcessor interface lets you intervene at four stages of a request:
postBeforeHttpMetadata – modify the request before it is sent (e.g., add signatures).
postSendingHttpRequest – customise the actual sending logic or log the request.
postAfterHttpResponseBodyString – process the raw response body string (e.g., decrypt).
postAfterHttpResponseBodyResult – manipulate the deserialized result before it is returned.
postAfterMethodReturnValue – final AOP‑style post‑processing of the method’s return value.
3.5 Custom HTTP Client
@Configuration
public class CustomConfiguration {
@Bean
public OkHttpClient myOkHttpClient() {
return new OkHttpClient.Builder()
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(20, 10, TimeUnit.MINUTES))
.build();
}
}4. Enterprise‑Level Channel Integration Example
4.1 Configuration (application.yml)
channel:
mtuan:
url: http://127.0.0.1:8999
appId: UUU-asd-01
publicKey: fajdkf9492304jklfahqq4.2 Custom Annotation
@HttpApi(processor = MTuanHttpApiProcessor.class)
public @interface MTuanHttpApi {
@AliasFor(annotation = HttpApi.class)
String url() default "${channel.mtuan.url}";
String appId() default "${channel.mtuan.appId}";
}4.3 API Definition
@MTuanHttpApi
public interface WeatherApi {
@GetHttpInterface("/getCityByName")
BaseRsp<WeatherDTO> getCityWeather(@QueryPar("city") String cityName);
@PostHttpInterface("/getToken")
HttpResponse<BaseRsp<TokenDTO>> getToken(@HeaderPar("appId") String appId,
@HeaderPar("publicKey") String publicKey);
}4.4 Processor Implementation (simplified)
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {
@Value("${channel.mtuan.publicKey}") private String publicKey;
@Value("${channel.mtuan.appId}") private String appId;
@Autowired private Environment env;
@Autowired private WeatherApi weatherApi;
@Override
public HttpMetadata postBeforeHttpMetadata(HttpMetadata meta, HttpApiMethodInvocation<MTuanHttpApi> inv) {
// add appId to query parameters
String resolvedAppId = env.resolvePlaceholders(inv.getProxyApiAnnotation().appId());
meta.putQueryParam("appId", resolvedAppId);
// generate sign and add to headers
String sign = createSignKey(meta.getHttpUrl().getQueryParam(), meta.getBody());
meta.putHeader("sign", sign);
return meta;
}
private String createSignKey(Map<String, Object> query, HttpBody body) {
// pseudo‑code: concatenate query string, body string and publicKey, then SHA‑256
String qs = query.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(";"));
String bs = (body instanceof HttpBodyJSON) ? body.toStringBody() : "";
String raw = publicKey + qs + bs;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(raw.getBytes());
return new String(digest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public HttpResponse<?> postSendHttpRequest(HttpSender sender, HttpMetadata meta) {
// obtain token and sessionId before the real request
HttpResponse<String> tokenResp = weatherApi.getToken(appId, publicKey);
String token = tokenResp.getBodyResult();
String sessionId = tokenResp.getHeader("sessionId");
meta.addCookie(new Cookie("token", token));
meta.addCookie(new Cookie("sessionId", sessionId));
return sender.sendHttpRequest(meta);
}
@Override
public Object postAfterHttpResponseBodyResult(Object bodyResult, HttpResponse<?> resp,
Method method, HttpMetadata meta) {
if (bodyResult instanceof BaseRsp) {
((BaseRsp) bodyResult).setCode(999);
}
return bodyResult;
}
}4.5 Usage
After the above setup, calling weatherApi.getCityWeather("Beijing") will automatically add the required appId, generate the sign header, fetch a fresh token and sessionId, and finally return the deserialized weather data.
5. Source Code
The full project is available on GitHub: https://github.com/burukeYou/UniAPI . Example usage can be found in the uniapi-test-http module.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
