# 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 ceps = List.of("01001000", "99999999", "20040020"); List
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).