Classe Date Legada do Java
A classe legada java.util.Date — por que foi substituída pelo java.time e como converter entre elas.
java.util.Date foi a classe de data e hora original do Java, presente desde o Java 1.0 em 1995. Ela ainda está no JDK; código novo não deveria usá-la, mas você a encontrará em bibliotecas, bancos de dados (java.sql.Date a estende) e em qualquer código anterior a aproximadamente 2014. Este capítulo existe para fazer a ponte com o java.time.
A versão resumida: Date é internamente um invólucro em torno de um long de milissegundos desde a época, assim como Instant é internamente um par (seconds, nanos). Portanto, a conversão natural é Date ↔ Instant. Para qualquer outra coisa (ano, mês, dia, aritmética de calendário), converta primeiro para java.time.
O que Date realmente é
public class Date implements Cloneable, Comparable<Date>, Serializable {
private long fastTime; // milliseconds since 1970-01-01T00:00:00Z
}Esse é o estado real completo. Um Date é um ponto no tempo, medido em milissegundos desde a época Unix, em UTC. Apesar do nome, ele não carrega uma data de calendário — getYear() e similares calculam a partir do valor em milissegundos no fuso horário padrão da JVM, que é a origem dos famosos problemas da API.
Date now = new Date(); // current moment
Date epoch = new Date(0); // 1970-01-01T00:00:00Z
Date fromMs = new Date(1_700_000_000_000L);
long ms = now.getTime(); // milliseconds since epochnew Date() e Date.getTime() são os dois métodos que envelheceram bem. Tudo o mais foi depreciado ou carrega uma armadilha.
Os acessores de calendário depreciados
Esses métodos foram depreciados no Java 1.1 (1997) quando Calendar foi adicionado:
date.getYear(); // year - 1900 (deprecated)
date.getMonth(); // 0-11 (deprecated)
date.getDate(); // 1-31, day of month (deprecated)
date.getDay(); // 0-6, day of week (deprecated)
date.getHours(); date.getMinutes(); date.getSeconds(); // local-zone reads (deprecated)As depreciações existem há 28 anos. Elas ainda funcionam. As armadilhas:
getYear()retornayear - 1900. Para 2025, retorna125. Esse é um candidato indiscutível ao título de "decisão de API mais estranha do JDK."getMonth()retorna 0-11. Janeiro é 0, Dezembro é 11. Erros de deslocamento por um são garantidos se você escrevergetMonth() + 1e esquecer uma vez.- Todo acessor lê no fuso horário padrão da JVM. O mesmo
Dateem duas máquinas com fusos diferentes dá resultados diferentes emgetDate().
Não chame esses métodos. No momento em que você se pegar prestes a usar date.getYear(), converta para Instant/ZonedDateTime e use os acessores modernos.
A ponte Date ↔ Instant
Date legacy = new Date();
Instant inst = legacy.toInstant(); // since Java 8
Instant other = Instant.parse("2025-11-04T19:30:00Z");
Date back = Date.from(other); // since Java 8toInstant() e Date.from(...) são os métodos de conversão modernos, adicionados com o java.time. São as únicas duas chamadas de java.util.Date que você deveria escrever em código novo.
A conversão tem perda em uma direção: Date tem precisão de milissegundos, Instant tem precisão de nanossegundos. A viagem de ida e volta Instant → Date → Instant trunca os nanos abaixo do milissegundo:
Instant high = Instant.parse("2025-11-04T19:30:00.123456789Z");
Instant low = Date.from(high).toInstant();
// low = 2025-11-04T19:30:00.123Z — the 456789 nanos are gonePara timestamps de servidor isso é aceitável; para captura de eventos de alta resolução, mantenha-se em Instant do início ao fim.
java.sql.Date e java.sql.Timestamp
Os tipos JDBC são subclasses de java.util.Date:
java.sql.Date— uma data sem hora (o componente de hora é forçado a 00:00:00 em algum fuso). Nome enganoso; por baixo ainda é um invólucro de milissegundos desde a época.java.sql.Time— uma hora sem data.java.sql.Timestamp— comoDate, mas com precisão de nanossegundos.
Todos têm métodos de conversão do JDK 8:
java.sql.Date sqlDate = java.sql.Date.valueOf(LocalDate.of(2025, 11, 4));
LocalDate localDate = sqlDate.toLocalDate();
java.sql.Timestamp ts = java.sql.Timestamp.from(Instant.now());
Instant inst = ts.toInstant();Drivers JDBC modernos também aceitam tipos java.time diretamente via setObject/getObject — para código novo, pule os tipos java.sql.* e use LocalDate/Instant. As conversões existem para código que precisa interoperar com um driver ou framework que ainda não migrou.
Comparação e ordenação
Date implementa Comparable<Date>. A ordem é por milissegundos desde a época — igual ao Instant. Portanto, ordenar uma List<Date> funciona da mesma forma que ordenar uma List<Instant>.
equals compara o long subjacente. Dois valores de Date são iguais se e somente se têm o mesmo valor em milissegundos. O hash funciona (é (int)(time ^ (time >>> 32))), então Date pode ser usado como chave de HashMap — embora, novamente, Instant seja a escolha moderna.
Mutabilidade
A maior armadilha oculta: Date é mutável.
Date d = new Date();
d.setTime(0); // mutates d in placeIsso significa que qualquer método que aceite ou retorne um Date é perigoso — quem chama pode alterar o valor após passá-lo; quem é chamado pode alterar o valor que o chamador mantém. Código de biblioteca faz cópia defensiva (new Date(d.getTime())) de todo Date que recebe. Esse tipo de contabilidade foi completamente eliminado pelo java.time, que tornou todos os tipos imutáveis.
Em código legado, trate qualquer campo Date como se pudesse mudar a qualquer momento. Converta para Instant na fronteira da API se precisar de um snapshot estável.
SimpleDateFormat: o outro item depreciado
Junto com Date em código antigo está o java.text.SimpleDateFormat:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String s = sdf.format(new Date());
Date d = sdf.parse("2025-11-04");SimpleDateFormat não é thread-safe. Compartilhá-lo entre threads produzirá saída incorreta, exceções ou ambos, de forma intermitente. A solução padrão em código legado é ThreadLocal<SimpleDateFormat>; o substituto moderno é DateTimeFormatter, que é thread-safe e pode ser armazenado em cache.
Se você mantém código com um static SimpleDateFormat, trate-o como um bug conhecido, independentemente de alguém ter relatado uma falha ou não.
Um exemplo prático: interoperabilidade legada
O programa abaixo usa Date como você o encontraria em uma API legada e mostra o caminho de conversão para java.time para cada operação. Leia-o como "aqui está a receita de migração": cada chamada legada tem um equivalente de uma linha com Instant ou ZonedDateTime.
O que observar na execução:
Date.toString()é exibido no fuso horário padrão da JVM. O mesmo valor deDateaparece de forma diferente em um servidor UTC vs. um laptop com America/New_York. Esse é o defeito central de design que motivou a reformulação dojava.time— o valor é UTC, a exibição é local, e a API não oferece uma forma fácil de saber qual visão você está vendo. Se você se importa com qual fuso horário você quer, converta paraZonedDateTimee forneça o fuso explicitamente.now.getYear()retornou125para 2025. A convençãoyear - 1900foi um erro do Java 1.0 que nunca foi corrigido; o método foi depreciado na versão 1.1 e ainda está lá. Qualquer chamada agetYear()em umDateque retorne "125" ou "85" é um bug esperando para aparecer para um usuário.Instant.parse("...nanoseconds")exibiu nove dígitos de precisão; o mesmo valor após ida e volta porDate.frome de volta perdeu os últimos seis. Para logs de servidor (precisão de milissegundos é suficiente), o truncamento não importa. Para "capturei este evento com temporização de alta precisão," não faça a viagem de ida e volta porDate.- O +1 dia legado era
now.getTime() + 24L * 60 * 60 * 1000— aritmética manual, fácil de errar (esquecer oLe causar overflow). O modernoinst.plus(Duration.ofDays(1))é seguro em relação a tipos e legível em voz alta. Ao migrar, substituir todo cálculotime + N * msporDurationé o ganho mais fácil. - A demonstração de mutabilidade no final mostrou que
shared.setTime(0)alterou o valor visto pelas duas referências. Em um código multithread isso é uma condição de corrida; em código de thread única ainda é um ritual de cópia defensiva que o JDK impôs a toda biblioteca. A API moderna nunca exige esse ritual.
O que vem a seguir
java.util.Date é metade da API legada. A outra metade é java.util.Calendar — a classe adicionada no Java 1.1 para dar a Date os acessores de calendário que ela mesma não deveria ter. O próximo capítulo, Java Calendar Class, é o último desta parte e o aborda com a mesma estrutura de receita de migração: cada chamada legada tem um substituto em java.time.