Response
Here's the controller, following the house Spring Boot conventions (constructor injection, wrapped `ApiResponse`, 1-based clamped pagination):
```java
package com.acme.billing;
import com.acme.common.ApiResponse;
import com.acme.common.PageMeta;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/billing")
public class InvoiceController {
private static final int MAX_PAGE_SIZE = 100;
private final InvoiceService invoiceService;
public InvoiceController(InvoiceService invoiceService) {
this.invoiceService = invoiceService;
}
@GetMapping("/invoices")
public ApiResponse<?> listInvoices(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
int pageNo = Math.max(page, 1);
int pageSize = Math.clamp(size, 1, MAX_PAGE_SIZE);
InvoiceQueryResult result = invoiceService.listInvoices(pageNo, pageSize);
return ApiResponse.ok(result.items(), PageMeta.of(pageNo, pageSize, result.total()));
}
}
```
Convention decisions applied: constructor injection (no field injection/Lombok), and the response wrapped as `ApiResponse.ok(items, PageMeta.of(...))` with 1-based `page`/`size` clamped to `[1, 100]` rather than passed straight to the service.
Note: this assumes `ApiResponse` and `PageMeta` already exist in `com.acme.common` (per the house convention) — since only `InvoiceService`/`InvoiceQueryResult` were given, I didn't define those shared types here.