diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-26 12:20:42 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-26 12:20:42 -0300 |
| commit | 329f679cd359bcab92adf3366cf17711836d0c79 (patch) | |
| tree | 68f956deb2e0d5c3a25da20260ee3c0678a64cd6 /README.md | |
| download | java-329f679cd359bcab92adf3366cf17711836d0c79.tar.gz | |
Initial importv0.1.0
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..599a25e --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ +# cepis + +Java client for the [public CEP API at cep.is](https://cep.is). Official +Correios data (DNE), refreshed every two weeks. + +```java +import is.cep.cepis.Address; +import is.cep.cepis.Cepis; + +Address address = Cepis.lookup("01001-000"); +System.out.println(address.logradouro() + " " + address.localidade() + " " + + address.uf()); +// Praça da Sé São Paulo SP +``` + +## Installation + +Requires Java 17 or newer. There are no dependencies: HTTP comes from +`java.net.http`, and the JSON and CSV readers are in the library because the +JDK has neither and the shapes this API returns are small enough not to justify +a jar. + +```sh +git clone https://git.cep.is/java.git cepis-java +cd cepis-java +make build +``` + +That writes `build/cepis-0.1.0.jar`; put it on the classpath. + +## Usage + +### A single lookup + +`lookup` accepts the CEP masked or unmasked and returns an `Address`: + +```java +Address a = Cepis.lookup("01310-100"); + +a.logradouro(); // "Avenida Paulista" +a.localidade(); // "São Paulo" +a.uf(); // "SP" +a.ibge(); // "3550308" +a.cep(); // "01310100" (always unmasked) +a.formattedCep(); // "01310-100" +``` + +The data fields keep the API's own Portuguese names, with only the casing +adapted: what you read in the JSON is what you access on the object. Fields the +database does not have for that CEP come back as empty strings, never `null`. + +### Coordinates + +About 57% of CEPs have a coordinate. When they do not, `geo()` is `null`: + +```java +Geo geo = a.geo(); +if (geo != null) { + geo.lat(); // -23.567202 + geo.lon(); // -46.649659 + geo.raioM(); // 1084 + geo.precisao(); // "logradouro" +} +``` + +**Check `precisao` before computing distance.** There are two kinds of point, +and the difference between them is tens of kilometres: + +| `precisao` | typical radius | what the point is | +| --- | --- | --- | +| `logradouro` | 110–140 m | the stretch of street — good for maps and distance | +| `localidade` | 7–40 km | the municipality centroid: the CEP *is* the whole city | + +`geo.isPrecise()` is the shorthand for `"logradouro".equals(precisao)`: + +```java +if (a.geo() != null && a.geo().isPrecise()) { + cost = shippingFrom(a.geo().lat(), a.geo().lon()); +} else { + cost = shippingFromMunicipality(a.ibge()); +} +``` + +### Many CEPs at once + +Do not loop over `lookup`. Use `lookupMany`, which resolves up to 500 CEPs per +request: + +```java +List<String> ceps = List.of("01001000", "99999999", "20040020"); +List<Address> result = Cepis.lookupMany(ceps); + +for (int i = 0; i < ceps.size(); i++) { + Address address = result.get(i); + System.out.println(address == null + ? ceps.get(i) + ": not found" + : ceps.get(i) + ": " + address.localidade()); +} +``` + +The returned list has **the same size and the same order** as the input. A CEP +that does not exist, or is malformed, becomes `null` at its position — it does +not vanish, and it does not throw. + +Lists longer than 500 are chunked automatically. + +> **Batches carry no coordinate.** The endpoint behind them is CSV, and the CSV +> has no `geo` column. Every `Address` from `lookupMany` has `geo() == null`, +> including for CEPs that do have one. Need the coordinate? Use `lookup`, one at +> a time. + +### API health + +Useful for two things: checking the API answers, and knowing **which version of +the database** is live: + +```java +Health h = Cepis.health(); +h.ok(); // true +h.dne(); // "26071" identifier of the fortnightly database +h.ceps(); // 1596168 +h.baseGeradaEm(); // "2026-07-24T03:48:28Z" +``` + +An API reporting itself degraded does not throw — that arrives in `h.status()`. + +## Configuration + +For anything beyond the defaults, build a `Client` and reuse it. It keeps no +state between calls, pools connections, and is safe to share across threads: + +```java +Client client = Client.builder() + .baseUrl("https://cep.is") // point at another instance + .timeout(Duration.ofSeconds(10)) // per attempt, connection included + .maxAttempts(3) // counting the first; 1 disables retrying + .backoffBase(Duration.ofMillis(500)) // base of the exponential wait + .backoffMax(Duration.ofSeconds(8)) // cap on each wait + .totalTimeout(Duration.ofSeconds(15)) // cap on the whole call; unset by default + .build(); + +Address address = client.lookup("01001000"); +``` + +Retries cover 429, 5xx and network failures, with exponential backoff and +jitter; 400 and 404 are never retried. See the javadoc on `Client.Builder` for +each setting. + +The static methods on `Cepis` use a shared default client. It applies +process-wide, including to third-party libraries that also use this package; +need a different configuration? Build your own. + +### Pointing at a local instance + +`baseUrl` is the only setting that usually changes between environments. The +API repo brings a local instance up with `just up`: + +```java +Client client = Client.builder().baseUrl("http://localhost:8080").build(); +``` + +From the command line, to confirm quickly that it answers: + +```sh +java -jar build/cepis-0.1.0.jar --base-url http://localhost:8080 01001000 +``` + +## Errors + +Everything derives from `CepisException`, which is unchecked. Below that, the +split is by **the decision you have to make**, not by the HTTP status that +caused it: + +| error | when | does retrying help? | +| --- | --- | --- | +| `InvalidCepException` | not 8 digits | no | +| `CepNotFoundException` | well-formed, not in the database | no | +| `RateLimitedException` | 429; carries `retryAfter()` | yes, later | +| `ServerErrorException` | 5xx; carries `status()` | yes, later | +| `NetworkException` | DNS, connection, timeout, TLS | yes, later | +| `InvalidResponseException` | unintelligible response | probably not | + +```java +try { + Address address = Cepis.lookup(cepFromForm); +} catch (CepNotFoundException e) { + // ask the user for another CEP +} catch (CepisException e) { + // network, server, rate limit: reschedule +} +``` + +Java allows one superclass, so these are not also `IllegalArgumentException` or +`NoSuchElementException`: one base type that catches every failure is worth +more. + +`InvalidResponseException` almost always means something between you and the +API is answering instead of it — a captive Wi-Fi portal, a corporate proxy, an +HTML error page. Useful as a diagnosis: the problem is not in your code. + +## Development + +Everything runs through `make` and the JDK. There is nothing to install first +and no dependency to resolve: + +```sh +make # list the targets +make test # run the suite against a local HTTP server +make lint # javac -Werror + javadoc doclint, touches nothing +make build # the jar into build/ +make docs # the API documentation into build/docs +make check # lint, then test, then build +``` + +The JDK ships no formatter, so there is no `format` target. + +## Licence + +ISC. See [LICENSE](LICENSE). |