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.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Simplify Java HTTP Calls with UniHttp: A Declarative Framework Guide

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: fajdkf9492304jklfahqq

4.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.

HTTP protocol diagram
HTTP protocol diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentspringHTTPDeclarative API
Java Architect Essentials
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.