// 04 · baterías incluidas de verdad
La librería estándar
En Java instalas Apache Commons, Jackson, Lombok y Logback antes de escribir la primera línea útil. En Go, la stdlib cubre el 90% — y está tan bien diseñada que el ecosistema entero imita su estilo.
// texto y unicode sin sustos
Strings, runes y bytes
Un string en Go es una secuencia inmutable de bytes, casi siempre UTF-8. La distinción clave que Java esconde y Go expone: len() cuenta bytes, no caracteres. Para caracteres reales (code points) están las runas.
s := "señal"
fmt.Println(len(s)) // 6 bytes (la ñ ocupa 2 en UTF-8)
fmt.Println(utf8.RuneCountInString(s)) // 5 caracteres reales
// range sobre un string itera por RUNAS (hace lo correcto con acentos):
for i, r := range "año" {
fmt.Printf("byte %d: %c\n", i, r) // byte 0: a · byte 1: ñ · byte 3: o
}
// El paquete strings: tu StringUtils de Apache Commons, de serie
strings.Contains("golang", "go") // true
strings.Split("a,b,c", ",") // [a b c]
strings.Join([]string{"a", "b"}, "-") // "a-b"
strings.TrimSpace(" hola ") // "hola"
strings.ReplaceAll("a-b-c", "-", "_") // "a_b_c"
strings.HasPrefix("img.png", "img") // true
strings.EqualFold("Go", "GO") // true (ignora mayúsculas)
// Concatenación en bucle: strings.Builder (tu StringBuilder)
var b strings.Builder
for i := 0; i < 3; i++ {
fmt.Fprintf(&b, "línea %d\n", i)
}
fmt.Println(b.String())La trampa del substring por índices[1] devuelve un byte, no un carácter, y s[0:2] puede partir una ñ por la mitad. Si el texto puede llevar acentos o emojis, convierte primero: []rune(s) — o mejor, trabaja con las funciones de strings que ya lo hacen bien.
// el formateo más raro que verás esta década
time: fechas, duraciones y timers
Un solo paquete cubre lo que en Java está repartido entre Instant, Duration, LocalDateTime y DateTimeFormatter. Con una rareza legendaria: no hay patrones tipo yyyy-MM-dd — se formatea mostrando cómo se vería la fecha de referencia: Mon Jan 2 15:04:05 MST 2006 (1-2-3-4-5-6-7 en orden estadounidense).
ahora := time.Now()
// Duraciones: tipadas, legibles, sin TimeUnit.MILLISECONDS
espera := 90 * time.Minute
manana := ahora.Add(24 * time.Hour)
fmt.Println(manana.Sub(ahora)) // 24h0m0s
fmt.Println(ahora.Before(manana)) // true
// Formateo: escribe la FECHA DE REFERENCIA con el aspecto que quieres.
// Referencia: 2006-01-02 15:04:05 (año 2006, mes 01, día 02...)
fmt.Println(ahora.Format("2006-01-02 15:04")) // 2026-07-28 19:30
fmt.Println(ahora.Format("02/01/2006")) // 28/07/2026
fmt.Println(ahora.Format(time.RFC3339)) // el ISO de las APIs
// Parsear: mismo sistema, con error si no encaja
t, err := time.Parse("02/01/2006", "28/07/2026")
// Medir cuánto tardó algo:
inicio := time.Now()
procesar()
fmt.Println("tardó", time.Since(inicio))
// Tickers para tareas periódicas (tu @Scheduled):
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
revisarCola()
}Zonas horariastime.Time siempre carga su zona. time.LoadLocation("America/Mexico_City") + t.In(loc) convierten; en servidores guarda y compara siempre en UTC (t.UTC()) y convierte solo al mostrar — la misma disciplina que con Instant en Java.
// tu Jackson viene de fábrica
JSON a fondo
encoding/json serializa por reflexión guiada por struct tags. Regla de oro que sorprende a todos: solo los campos exportados (Mayúscula) se serializan — un campo privado es invisible para el paquete json.
type Perfil struct {
Nombre string `json:"nombre"`
Bio string `json:"bio,omitempty"` // se omite si es ""
Edad *int `json:"edad,omitempty"` // puntero: distingue 0 de "no enviado"
Password string `json:"-"` // JAMÁS sale al JSON
Extra map[string]any `json:"extra,omitempty"`
}
// struct → JSON
p := Perfil{Nombre: "Daniel"}
datos, _ := json.Marshal(p) // {"nombre":"Daniel"}
bonito, _ := json.MarshalIndent(p, "", " ") // con indentación
// JSON → struct (los campos desconocidos se ignoran en silencio...)
var q Perfil
err := json.Unmarshal([]byte(`{"nombre":"Ana","edad":28}`), &q)
// ...salvo que actives el modo estricto, ideal para APIs:
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields() // campo inesperado = error 400 para el cliente
err = dec.Decode(&q)
// json.RawMessage: pospone el parseo de una parte (payloads polimórficos)
type Evento struct {
Tipo string `json:"tipo"`
Payload json.RawMessage `json:"payload"` // se parsea según Tipo, después
}El mapa Jackson → Go@JsonProperty → tag json:"..." · @JsonIgnore → json:"-" · @JsonInclude(NON_NULL) → ,omitempty · serializer custom → implementar MarshalJSON() ([]byte, error) en tu tipo. Sin ObjectMapper que configurar ni inyectar: son funciones de paquete.
El matiz de omitempty + números (y cómo lo resuelve el puntero)
omitempty omite el zero value: para un int, eso incluye el 0 legítimo. Si tu API debe distinguir "el cliente envió 0" de "el cliente no envió nada" (el eterno problema de los PATCH), usa un puntero: *int es nil cuando el campo no vino, y *edad == 0 cuando vino un cero real. Es el Integer vs int de Java, pero elegido a propósito y solo donde hace falta.
// las dos interfaces que sostienen todo
Archivos e I/O: el poder de io.Reader
Todo el sistema de I/O de Go se apoya en dos interfaces de un método: . Archivos, respuestas HTTP, buffers, gzip, cifrado, hashes… todo las implementa, así que todo se conecta con todo, como tuberías Unix.
// Lo simple, simple (archivos que caben en memoria):
datos, err := os.ReadFile("config.json")
err = os.WriteFile("salida.txt", []byte("hola"), 0o644)
// Línea a línea sin cargar todo (logs gigantes):
f, err := os.Open("acceso.log")
if err != nil { return err }
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
linea := sc.Text()
if strings.Contains(linea, "ERROR") {
fmt.Println(linea)
}
}
// io.Copy: streaming de Reader a Writer sin tocar RAM de más.
// ¿Descargar un archivo a disco? Tres líneas y cero buffers manuales:
res, _ := http.Get(url)
defer res.Body.Close()
io.Copy(archivoDestino, res.Body)
// Rutas multiplataforma (tu Windows vs el servidor Linux):
ruta := filepath.Join("datos", "2026", "reporte.csv")// Leer un CSV comprimido descargado por HTTP, en streaming:
// cada capa envuelve a la anterior, como los decoradores de java.io
// pero sin jerarquías de 40 clases.
res, _ := http.Get("https://ejemplo.com/ventas.csv.gz")
defer res.Body.Close()
gz, _ := gzip.NewReader(res.Body) // Reader sobre Reader
defer gz.Close()
lector := csv.NewReader(gz) // Reader sobre Reader sobre Reader
for {
fila, err := lector.Read()
if err == io.EOF { break }
procesar(fila)
}// logging estructurado de serie (Go 1.21)
log/slog
Tu Logback + MDC + encoder JSON, sin XML: log/slog hace logging estructurado por pares clave-valor, con niveles, y elige formato con un handler. Es el estándar moderno — si ves log.Printf en código nuevo, es código con acento antiguo.
// JSON a stdout: listo para Loki/Datadog/CloudWatch
log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
slog.SetDefault(log) // también captura al viejo paquete "log"
slog.Info("pago procesado", "orden", 4211, "monto", 1499.90, "moneda", "MXN")
// → {"time":"...","level":"INFO","msg":"pago procesado","orden":4211,...}
slog.Error("fallo al cobrar", "err", err, "orden", 4211)
// Logger hijo con atributos fijos (tu MDC, pero explícito):
logPagos := log.With("servicio", "pagos", "version", "1.4.2")
logPagos.Warn("reintentando", "intento", 3)
// Agrupar campos anidados:
slog.Info("request",
slog.Group("http", "metodo", "POST", "ruta", "/pagos", "status", 201),
)Regla de producciónJSON handler + nivel por variable de entorno + un With("request_id", id) por petición en tu middleware. Con eso tienes trazabilidad completa en cualquier agregador de logs, sin ninguna dependencia externa.
// el índice mental
Los paquetes que debes conocer
| Paquete | Qué resuelve | Tu referencia mental |
|---|---|---|
fmt | Formateo e impresión (%v %s %d %+v %T) | String.format + System.out |
strconv | String ↔ números/bool | Integer.parseInt y familia |
errors | Crear, envolver e inspeccionar errores | Jerarquías de excepciones |
slices, maps | Utilidades genéricas de colecciones (1.21+) | Collections + parte de Streams |
sort | Ordenar con comparadores custom | Comparator.comparing |
math/rand/v2 | Aleatorios (la v2 es la API buena) | Random / ThreadLocalRandom |
net/url | Parsear y construir URLs con escape correcto | URI + URLEncoder |
encoding/csv, encoding/base64 | CSV y base64 sin librerías | OpenCSV, Base64 |
regexp | Regex (sintaxis RE2: sin backtracking catastrófico) | Pattern/Matcher |
crypto/* | SHA, HMAC, TLS, aleatorios seguros | MessageDigest, javax.crypto |
os/exec | Ejecutar comandos externos | ProcessBuilder |
testing, net/http/httptest | Tests, benchmarks, fuzzing, servidor fake | JUnit + MockMvc |