Java Formatar Texto

Mostrar 9.5 como 9,50, alinhar uma tabela de alunos em colunas, escrever R$ 1.234,56: tudo isso é formatar. O Java tem um "molde" de texto, com lacunas como %d e %.2f, que é usado em três lugares: String.format, printf e formatted. E tem um detalhe que pega todo brasileiro: o idioma do computador muda a vírgula e o ponto.

Você vai aprender
  • Usar String.format, System.out.printf e "…".formatted(…).
  • Os especificadores: %s %d %f %.2f %5d %-10s %05d %,d %x %e %n %%.
  • Por que, num computador em português, %.2f mostra vírgula, e como forçar o ponto com Locale.US.
  • Montar tabelas alinhadas.
  • Formatar dinheiro com NumberFormat e o espaço "invisível" de R$ 1.234,56.
  • As exceções de formato mais comuns.

A ideia antes do código

Pense num formulário de papel: "Eu, ________, de ___ anos, declaro…". O texto fixo já está impresso; você só preenche as lacunas. Cada lacuna tem um formato esperado: o nome é texto, a idade é número.

No Java, o formulário é uma String com especificadores de formato (as lacunas), que começam com %: "%s tem %d anos". Depois do formulário, você passa os valores, na mesma ordem das lacunas.


Três formas de usar o mesmo molde

FormaO que fazExemplo
String.format(molde, valores…)Devolve uma String prontaString s = String.format("%d anos", 29);
System.out.printf(molde, valores…)Mostra na tela (não pula linha sozinho)System.out.printf("%d anos%n", 29);
molde.formatted(valores…) Java 15+Igual ao String.format, escrito a partir do moldeString s = "%d anos".formatted(29);

O printf vem da linguagem C ("print formatted", imprimir formatado). System.out.format é um sinônimo dele. As três formas entendem exatamente os mesmos especificadores.

String nome = "Ana";
int idade = 29;
System.out.printf("%s tem %d anos%n", nome, idade);
"%s tem %d anos%n"
O molde. Tem três especificadores.
%s
Lacuna para texto (string). Recebe o primeiro valor, nome.
%d
Lacuna para número inteiro (decimal, "na base 10"). Recebe o segundo valor, idade.
%n
Quebra de linha (new line). O printf não pula linha sozinho como o println. Não consome valor nenhum.
nome, idade
Os valores, na ordem das lacunas.

Os especificadores

Um programa que mostra os mais usados, rodado na máquina da Ana (em português do Brasil):

public class Main {
    public static void main(String[] args) {
        String nome = "Ana";
        int idade = 29;
        double nota = 9.5;
        System.out.printf("%s tem %d anos%n", nome, idade);
        System.out.printf("Nota: %f%n", nota);
        System.out.printf("Nota: %.2f%n", nota);
        System.out.printf("Nota: %.0f%n", nota);
        System.out.printf("Pi: %.4f%n", Math.PI);
        System.out.printf("[%5d]%n", 42);
        System.out.printf("[%-5d]%n", 42);
        System.out.printf("[%05d]%n", 42);
        System.out.printf("[%10s]%n", nome);
        System.out.printf("[%-10s]%n", nome);
        System.out.printf("%,d%n", 1234567);
        System.out.printf("%,.2f%n", 1234567.891);
        System.out.printf("%x %X%n", 255, 255);
        System.out.printf("%e%n", 1234567.891);
        System.out.printf("Desconto de 10%%%n");
        System.out.printf("%b %c%n", true, 'A');
        System.out.println(nota);
    }
}
$ java Main.java
Ana tem 29 anos
Nota: 9,500000
Nota: 9,50
Nota: 10
Pi: 3,1416
[   42]
[42   ]
[00042]
[       Ana]
[Ana       ]
1.234.567
1.234.567,89
ff FF
1,234568e+06
Desconto de 10%
true A
9.5

Os colchetes [ ] fazem parte do texto fixo: estão ali só para você ver os espaços. Linha a linha:

EspecificadorValorSaídaO que fez
%f9.59,500000Número com casas decimais (float). Sem precisão, mostra 6 casas
%.2f9.59,50.2 = duas casas decimais. O mais usado de todos
%.0f9.510Zero casas: arredonda (9,5 sobe para 10)
%.4fMath.PI3,1416Quatro casas, arredondando
%5d42[ 42]Largura 5: ocupa pelo menos 5 posições, alinhado à direita
%-5d42[42 ]O - alinha à esquerda
%05d42[00042]O 0 completa com zeros (códigos, matrículas)
%10s / %-10s"Ana"[ Ana] / [Ana ]Largura também vale para texto
%,d12345671.234.567A , liga o separador de milhar
%,.2f1234567.8911.234.567,89Milhar e duas casas juntos
%x / %X255ff / FFHexadecimal (base 16), minúsculo ou maiúsculo
%e1234567.8911,234568e+06Notação científica: 1,234568 × 106
%%—%Um sinal de porcentagem de verdade
%b / %ctrue / 'A'true / Aboolean e caractere (char)
%n—quebra de linhaFunciona em qualquer sistema

A anatomia de um especificador

Todos seguem a mesma ordem: %, depois (opcionais) as opções, a largura e a precisão, e por fim a conversão (a letra):

%
Começo da lacuna.
- 0 , +
Opções (flags): - alinha à esquerda; 0 completa com zeros; , separa milhares; + mostra o sinal também nos positivos.
10
Largura mínima, em caracteres.
.2
Precisão: casas decimais (em %f) ou máximo de caracteres (em %s: %.3s mostra só 3 letras).
f
A conversão: s, d, f, x, e, b, c…

Exemplo completo: %,10.2f = separador de milhar, largura 10, duas casas, número decimal.

Missão: complete os dois printf: o primeiro deve mostrar 12 x Pão de queijo (use %d e %s) e o segundo, Preço: R$ 7,50 (duas casas decimais). Não esqueça o %n.

public class Main {
    public static void main(String[] args) {
        String produto = "Pão de queijo";
        int quantidade = 12;
        double preco = 7.5;
        System.out.printf("", quantidade, produto);
        System.out.printf("", preco);
    }
}
public class Main {
    public static void main(String[] args) {
        String produto = "Pão de queijo";
        int quantidade = 12;
        double preco = 7.5;
        System.out.printf("%d x %s%n", quantidade, produto);
        System.out.printf("Preço: R$ %.2f%n", preco);
    }
}

Vírgula ou ponto? O locale

Repare de novo na saída acima: %.2f mostrou 9,50, com vírgula, mas o println(nota) da última linha mostrou 9.5, com ponto. Por quê?

O String.format, o printf e o formatted seguem o locale (pronuncia-se "lôu-quél"): a configuração de idioma e país do computador. A máquina da Ana está em pt_BR (português do Brasil), onde a vírgula separa os decimais e o ponto separa os milhares. Já o println e o + com texto ignoram o locale e usam sempre o ponto, o formato do próprio Java.

import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        double valor = 1234567.891;
        System.out.println(Locale.getDefault());
        System.out.println(String.format("%,.2f", valor));
        System.out.println(String.format(Locale.US, "%,.2f", valor));
        System.out.println(String.format(Locale.GERMANY, "%,.2f", valor));
        System.out.println(String.format(Locale.FRANCE, "%,.2f", valor));
        System.out.println("%.2f".formatted(valor));
        System.out.println(valor);
    }
}
$ java Main.java
pt_BR
1.234.567,89
1,234,567.89
1.234.567,89
1 234 567,89
1234567,89
1234567.891
Locale.getDefault()
O locale padrão da JVM, que ela pega do sistema: pt_BR (idioma pt, país BR). A classe Locale fica em java.util, por isso o import.
String.format("%,.2f", valor)
Sem dizer o locale: usa o padrão. 1.234.567,89.
String.format(Locale.US, …)
Passando um locale como primeiro argumento, ele vale só para esta chamada. Estados Unidos: 1,234,567.89, o contrário do Brasil.
Locale.GERMANY, Locale.FRANCE
A Alemanha usa o mesmo formato do Brasil; a França separa milhares com um espaço (um espaço especial, que não quebra a linha).
"%.2f".formatted(valor)
O formatted usa sempre o locale padrão: não tem versão com Locale.
println(valor)
Sem formatação nenhuma: o formato do Java, com ponto e todas as casas.
O mesmo programa, saídas diferentes

Um programa com printf("%.2f") mostra 9,50 no computador da Ana e 9.50 num servidor configurado em inglês. Quando o texto vai ser lido por pessoas, isso é bom: cada uma vê no formato do seu país. Quando o texto vai ser lido por outro programa (um arquivo CSV, um JSON, um número que será convertido de volta com Double.parseDouble, que só aceita ponto), use um locale fixo: String.format(Locale.US, …) ou Locale.ROOT (o locale "neutro").

Missão: no JShell, formate 3.14159 com duas casas usando ponto decimal, passando Locale.US para o String.format. (O JShell já importa java.util, então Locale funciona direto.) Experimente também sem o locale e veja a vírgula.

String.format(Locale.US, "%.2f", 3.14159)

Na máquina da Ana, as duas versões ficam assim:

$ jshell
|  Welcome to JShell -- Version 25.0.4.1
|  For an introduction type: /help intro

jshell> String.format("%.2f", 3.14159)
$1 ==> "3,14"

jshell> String.format(Locale.US, "%.2f", 3.14159)
$2 ==> "3.14"

jshell> /exit
|  Goodbye

Tabelas alinhadas

Com largura e alinhamento, a lista de alunos da Ana (a mesma do arquivo Documentos/alunos.csv) vira uma tabela. A regra prática: texto à esquerda (%-8s), números à direita (%5d, %5.1f), e a mesma largura no cabeçalho e nas linhas.

public class Main {
    public static void main(String[] args) {
        String[] nomes = {"Ana", "Bruno", "Carla", "Diego", "Elisa"};
        int[] idades = {29, 34, 22, 41, 27};
        String[] cidades = {"Recife", "Curitiba", "Salvador", "Manaus", "Porto Alegre"};
        double[] notas = {9.5, 7.0, 8.5, 6.0, 10.0};

        System.out.printf("%-8s %5s  %-14s %5s%n", "Nome", "Idade", "Cidade", "Nota");
        System.out.println("-".repeat(36));
        for (int i = 0; i < nomes.length; i++) {
            System.out.printf("%-8s %5d  %-14s %5.1f%n", nomes[i], idades[i], cidades[i], notas[i]);
        }
    }
}
$ java Main.java
Nome     Idade  Cidade          Nota
------------------------------------
Ana         29  Recife           9,5
Bruno       34  Curitiba         7,0
Carla       22  Salvador         8,5
Diego       41  Manaus           6,0
Elisa       27  Porto Alegre    10,0
"%-8s %5s %-14s %5s%n"
O cabeçalho usa %s em todas as colunas (são todos textos), mas com as mesmas larguras das linhas de dados, para as colunas baterem. "Idade" e "Nota" ficam à direita, em cima dos números.
"-".repeat(36)
Uma linha de 36 traços (a soma das larguras e dos espaços).
%5.1f
Nota com uma casa, ocupando 5 posições, alinhada à direita: 9,5 e 10,0 terminam na mesma coluna.

A largura é um mínimo: um nome com mais de 8 letras empurraria o resto da linha. Escolha larguras que caibam o maior valor. (Letras acentuadas contam como um caractere cada, então Maçã ocupa 4 posições, como esperado.)


Dinheiro: NumberFormat

Para moeda, em vez de montar "R$ " + String.format("%,.2f", …), use o NumberFormat (pacote java.text), que conhece o símbolo, a posição do sinal e as casas de cada moeda:

import java.text.NumberFormat;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        double preco = 1234.56;
        NumberFormat real = NumberFormat.getCurrencyInstance(Locale.of("pt", "BR"));
        NumberFormat dolar = NumberFormat.getCurrencyInstance(Locale.US);
        NumberFormat porcento = NumberFormat.getPercentInstance();
        System.out.println(real.format(preco));
        System.out.println(dolar.format(preco));
        System.out.println(real.format(-5));
        System.out.println(porcento.format(0.256));
        String texto = real.format(preco);
        System.out.println(texto.equals("R$ 1.234,56"));
        System.out.println((int) texto.charAt(2));
    }
}
$ java Main.java
R$ 1.234,56
$1,234.56
-R$ 5,00
26%
false
160
NumberFormat.getCurrencyInstance(locale)
"Formatador de moeda" do país indicado. Locale.of("pt", "BR") cria o locale do Brasil Java 19+.
real.format(preco)
Devolve a String formatada: R$ 1.234,56. Sempre duas casas: -5 vira -R$ 5,00.
getPercentInstance()
Porcentagem no locale padrão: multiplica por 100 e arredonda: 0.256 vira 26%.

O espaço que não é espaço

As duas últimas linhas mostram uma surpresa: texto.equals("R$ 1.234,56") deu false, embora o texto pareça idêntico. O caractere na posição 2 (entre o $ e o 1) tem código 160, e não 32, o espaço comum. É o espaço não separável (non-breaking space, \u00A0), usado para o "R$" nunca ficar sozinho no fim de uma linha. Olhando os bytes com o od:

$ java Main.java | head -1 | od -c | head -2
0000000   R   $ 302 240   1   .   2   3   4   ,   5   6  \n
0000015

Os bytes 302 240 são esse caractere em UTF-8. Na tela, ele é igual a um espaço. Em testes e comparações, lembre-se dele: compare com "R$\u00A01.234,56" ou troque antes com texto.replace('\u00A0', ' ').

Dinheiro de verdade

O NumberFormat resolve a exibição. Para calcular com dinheiro, não use double: 0.1 + 0.2 não dá exatamente 0.3. Veja BigDecimal e Dinheiro (o NumberFormat formata BigDecimal também).


Qual usar

Você quer…Use
Mostrar algo formatado na telaSystem.out.printf(…%n)
Guardar o texto formatado numa variávelString.format(…) ou "…".formatted(…)
Número com ponto, para outro programa lerString.format(Locale.US, …) ou Locale.ROOT
MoedaNumberFormat.getCurrencyInstance(Locale.of("pt", "BR"))
PorcentagemNumberFormat.getPercentInstance() ou %.1f%%
Juntar duas ou três coisas sem formato especial+ (veja Concatenação)
DatasDateTimeFormatter (veja Datas e Horas)

Quando dá errado

Os erros de formato não são pegos pelo javac: o molde é só uma String para ele. Eles aparecem na execução, como exceções. Repare que o texto antes da lacuna problemática chega a ser mostrado.

MissingFormatArgumentException: faltou valor
public class Main {
    public static void main(String[] args) {
        System.out.printf("%s tem %d anos%n", "Ana");
    }
}
$ java Main.java
Ana tem Exception in thread "main" java.util.MissingFormatArgumentException: Format specifier '%d'
	at java.base/java.util.Formatter.format(Formatter.java:2760)
	at java.base/java.io.PrintStream.format(PrintStream.java:1183)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1081)
	at Main.main(Main.java:3)

O que aconteceu: "falta argumento para o especificador %d". Há duas lacunas e um valor só. O Ana tem foi escrito antes de o problema ser percebido.

Como resolver: conte as lacunas (sem contar %n e %%) e os valores: devem ser iguais.

IllegalFormatConversionException: d != java.lang.String
public class Main {
    public static void main(String[] args) {
        System.out.printf("Idade: %d%n", "29");
    }
}
$ java Main.java
Idade: Exception in thread "main" java.util.IllegalFormatConversionException: d != java.lang.String
	at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4662)
	at java.base/java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:3200)
	at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3155)
	at java.base/java.util.Formatter.format(Formatter.java:2761)
	at java.base/java.io.PrintStream.format(PrintStream.java:1183)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1081)
	at Main.main(Main.java:3)

O que aconteceu: "d é diferente de String": a lacuna %d espera um inteiro e recebeu o texto "29" (entre aspas!).

Como resolver: passe um número (29), converta (Integer.parseInt("29")) ou use %s.

f != java.lang.Integer: %f com número inteiro
public class Main {
    public static void main(String[] args) {
        int total = 30;
        System.out.printf("Média: %.2f%n", total / 4);
    }
}
$ java Main.java
Média: Exception in thread "main" java.util.IllegalFormatConversionException: f != java.lang.Integer
	at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4662)
	at java.base/java.util.Formatter$FormatSpecifier.printFloat(Formatter.java:3213)
	at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3161)
	at java.base/java.util.Formatter.format(Formatter.java:2761)
	at java.base/java.io.PrintStream.format(PrintStream.java:1183)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1081)
	at Main.main(Main.java:4)

O que aconteceu: total / 4 é uma divisão de inteiros: dá 7, um int. O %f não aceita inteiros (nem converte sozinho).

Como resolver: faça a conta com double: total / 4.0 (que dá 7,50). Veja Operadores Aritméticos.

Tudo grudado numa linha e UnknownFormatConversionException
public class Main {
    public static void main(String[] args) {
        System.out.printf("Nota: %.2f", 9.5);
        System.out.printf("Nota: %.2f", 7.0);
        System.out.println();
        System.out.printf("Desconto: 10%");
    }
}
$ java Main.java
Nota: 9,50Nota: 7,00
Exception in thread "main" java.util.UnknownFormatConversionException: Conversion = '%'
	at java.base/java.util.Formatter.parse(Formatter.java:2800)
	at java.base/java.util.Formatter.format(Formatter.java:2744)
	at java.base/java.io.PrintStream.format(PrintStream.java:1183)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1081)
	at Main.main(Main.java:6)

O que aconteceu: dois problemas. Sem %n, as notas saíram coladas. E um % sozinho no fim do molde foi lido como o começo de uma lacuna sem letra: "conversão desconhecida".

Como resolver: termine cada printf com %n, e escreva %% para um sinal de porcentagem.

"Locale(String,String) in Locale has been deprecated"
$ java Main.java
Main.java:6: warning: [deprecation] Locale(String,String) in Locale has been deprecated
        NumberFormat real = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
                                                             ^
1 warning
R$ 10,00

O que aconteceu: não é erro, é um aviso: o programa rodou (R$ 10,00). O construtor new Locale("pt", "BR"), que aparece em muitos tutoriais, foi marcado como obsoleto (deprecated) no Java 19.

Como resolver: use Locale.of("pt", "BR"). Em Java 17 ou anterior, new Locale("pt", "BR") continua sendo o jeito certo.


Referência

EspecificadorParaExemplo (pt_BR)
%sTexto (qualquer objeto)Ana
%dInteiros (int, long…)29
%f, %.2fDecimais (double, float)9,500000, 9,50
%5d, %-10sLargura, alinhamento à esquerda[ 42], [Ana ]
%05dCompletar com zeros00042
%,d, %,.2fSeparador de milhar1.234.567, 1.234.567,89
%x, %XHexadecimalff, FF
%eNotação científica1,234568e+06
%b, %cboolean, chartrue, A
%nQuebra de linha
%%O caractere %%
String.format(Locale.US, …)Forçar ponto decimal3.14
NumberFormat.getCurrencyInstance(Locale.of("pt","BR"))MoedaR$ 1.234,56 (com \u00A0)

Exercícios

1. Duas casas

Complete o especificador para mostrar o double com duas casas decimais:

System.out.printf("Total: %n", total);

2. Matrícula com zeros

Complete para o número 37 aparecer como 000037 (6 dígitos):

String.format("", 37)

3. Porcentagem

Na máquina da Ana, o que String.format("%.1f%%", 12.34) devolve?

4. Num computador em pt_BR, o que System.out.println(2.5) e System.out.printf("%.1f%n", 2.5) mostram?

5. Que exceção String.format("%d", 9.5) lança?

6. Para gravar 9.5 num arquivo CSV que outro programa vai ler com Double.parseDouble, qual é o certo?

Missão final: monte a tabela de preços da feira com um único printf dentro do laço: nome alinhado à esquerda em 10 posições, um espaço, R$ e o preço com duas casas em 6 posições. A saída deve ser exatamente:

Maçã       R$   3,50
Banana     R$   4,99
Melancia   R$  12,00
public class Main {
    public static void main(String[] args) {
        String[] produtos = {"Maçã", "Banana", "Melancia"};
        double[] precos = {3.5, 4.99, 12.0};
        for (int i = 0; i < produtos.length; i++) {
            System.out.println(produtos[i] + " R$ " + precos[i]);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        String[] produtos = {"Maçã", "Banana", "Melancia"};
        double[] precos = {3.5, 4.99, 12.0};
        for (int i = 0; i < produtos.length; i++) {
            System.out.printf("%-10s R$ %6.2f%n", produtos[i], precos[i]);
        }
    }
}

Resumo

  • Um molde com lacunas %… e os valores na ordem: String.format devolve, printf mostra, formatted (Java 15+) devolve.
  • Especificador = % + opções (- 0 , +) + largura + .precisão + letra (s d f x e b c). %n pula linha; %% é o próprio %.
  • A formatação segue o locale: em pt_BR, %.2f dá 9,50 e %,d dá 1.234.567. O println ignora o locale e usa ponto.
  • Para ponto fixo (arquivos, outros programas), passe Locale.US ou Locale.ROOT como primeiro argumento.
  • Tabelas: texto com %-Ns, números com %Nd/%N.2f, mesmas larguras no cabeçalho.
  • Moeda: NumberFormat.getCurrencyInstance(Locale.of("pt", "BR")) → R$ 1.234,56, com um espaço não separável (\u00A0) depois do R$.
  • Erros de formato são exceções em tempo de execução: MissingFormatArgumentException, IllegalFormatConversionException, UnknownFormatConversionException.