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

strings.go
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).

time.go
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()
}
i

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.

json.go
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.

archivos.go
// 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")
composicion.go — por qué io.Reader es genial
// 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.

slog.go
// 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),
)
i

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

PaqueteQué resuelveTu referencia mental
fmtFormateo e impresión (%v %s %d %+v %T)String.format + System.out
strconvString ↔ números/boolInteger.parseInt y familia
errorsCrear, envolver e inspeccionar erroresJerarquías de excepciones
slices, mapsUtilidades genéricas de colecciones (1.21+)Collections + parte de Streams
sortOrdenar con comparadores customComparator.comparing
math/rand/v2Aleatorios (la v2 es la API buena)Random / ThreadLocalRandom
net/urlParsear y construir URLs con escape correctoURI + URLEncoder
encoding/csv, encoding/base64CSV y base64 sin libreríasOpenCSV, Base64
regexpRegex (sintaxis RE2: sin backtracking catastrófico)Pattern/Matcher
crypto/*SHA, HMAC, TLS, aleatorios segurosMessageDigest, javax.crypto
os/execEjecutar comandos externosProcessBuilder
testing, net/http/httptestTests, benchmarks, fuzzing, servidor fakeJUnit + MockMvc