or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

dependency-injection.mdform-processing.mdformatting.mdindex.mdrouting.mdstreaming.mdutilities.mdvalidation.md

formatting.mddocs/

0

# Value Formatting and Conversion

1

2

Play Framework's formatting system provides type-safe value parsing and formatting capabilities with extensible formatter registration. The system handles conversion between strings and typed objects with locale support, enabling automatic form data binding and custom type handling.

3

4

## Capabilities

5

6

### Core Formatting Operations

7

8

Central formatting utilities for parsing and printing values with type safety and locale support.

9

10

```java { .api }

11

/**

12

* Helper class for parsing and formatting values with type safety

13

*/

14

public class Formatters {

15

/** Parse string to specified type using default locale */

16

public static <T> T parse(String text, Class<T> clazz);

17

18

/** Parse string to specified type with field context */

19

public static <T> T parse(Field field, String text, Class<T> clazz);

20

21

/** Convert typed value to string representation */

22

public static <T> String print(T t);

23

24

/** Convert typed value to string with field context */

25

public static <T> String print(Field field, T t);

26

27

/** Convert typed value to string with type descriptor */

28

public static <T> String print(TypeDescriptor desc, T t);

29

30

/** Register simple formatter for a type */

31

public static <T> void register(Class<T> clazz, SimpleFormatter<T> formatter);

32

33

/** Register annotation-based formatter for a type */

34

public static <A extends Annotation,T> void register(Class<T> clazz, AnnotationFormatter<A,T> formatter);

35

36

/** The underlying Spring conversion service */

37

public static final FormattingConversionService conversion;

38

}

39

```

40

41

**Usage Examples:**

42

43

```java

44

import play.data.format.Formatters;

45

import java.util.Date;

46

import java.math.BigDecimal;

47

48

// Basic parsing and printing

49

String numberStr = "123.45";

50

BigDecimal number = Formatters.parse(numberStr, BigDecimal.class);

51

String formatted = Formatters.print(number); // "123.45"

52

53

// Date parsing with locale

54

String dateStr = "2023-12-25";

55

Date date = Formatters.parse(dateStr, Date.class);

56

String dateFormatted = Formatters.print(date); // Locale-specific format

57

58

// Custom type conversion

59

public class Temperature {

60

private double celsius;

61

public Temperature(double celsius) { this.celsius = celsius; }

62

public double getCelsius() { return celsius; }

63

}

64

65

// Register custom formatter

66

Formatters.register(Temperature.class, new SimpleFormatter<Temperature>() {

67

@Override

68

public Temperature parse(String text, Locale locale) {

69

return new Temperature(Double.parseDouble(text));

70

}

71

72

@Override

73

public String print(Temperature temp, Locale locale) {

74

return String.format("%.1f°C", temp.getCelsius());

75

}

76

});

77

```

78

79

### Simple Formatter Framework

80

81

Base class for creating simple, locale-aware formatters for custom types.

82

83

```java { .api }

84

/**

85

* Base class for simple formatters that handle parsing and printing

86

*/

87

public abstract class Formatters.SimpleFormatter<T> {

88

/** Parse text representation to typed object */

89

public abstract T parse(String text, Locale locale) throws ParseException;

90

91

/** Format typed object to text representation */

92

public abstract String print(T t, Locale locale);

93

}

94

```

95

96

**Usage Examples:**

97

98

```java

99

import play.data.format.Formatters.SimpleFormatter;

100

101

// Custom currency formatter

102

public class CurrencyFormatter extends SimpleFormatter<Money> {

103

@Override

104

public Money parse(String text, Locale locale) throws ParseException {

105

// Remove currency symbols and parse

106

String cleanText = text.replaceAll("[^\\d.,]", "");

107

BigDecimal amount = new BigDecimal(cleanText);

108

return new Money(amount, Currency.getInstance(locale));

109

}

110

111

@Override

112

public String print(Money money, Locale locale) {

113

NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);

114

return formatter.format(money.getAmount());

115

}

116

}

117

118

// Register the formatter

119

Formatters.register(Money.class, new CurrencyFormatter());

120

121

// Usage in forms

122

public class ProductForm {

123

public String name;

124

public Money price; // Will use CurrencyFormatter automatically

125

}

126

```

127

128

### Annotation-Based Formatting

129

130

Advanced formatter framework for annotation-driven formatting with custom configuration.

131

132

```java { .api }

133

/**

134

* Base class for annotation-based formatters with custom configuration

135

*/

136

public abstract class Formatters.AnnotationFormatter<A extends Annotation, T> {

137

/** Parse text with annotation configuration */

138

public abstract T parse(A annotation, String text, Locale locale) throws ParseException;

139

140

/** Format value with annotation configuration */

141

public abstract String print(A annotation, T value, Locale locale);

142

}

143

```

144

145

### Built-in Format Types

146

147

Pre-built formatters and annotations for common data types.

148

149

```java { .api }

150

/**

151

* Container for built-in formatters and format annotations

152

*/

153

public class Formats {

154

// Built-in formatters are automatically registered

155

}

156

157

/**

158

* Date formatting with custom pattern support

159

*/

160

public class Formats.DateFormatter extends Formatters.SimpleFormatter<Date> {

161

/** Create date formatter with specific pattern */

162

public DateFormatter(String pattern);

163

164

/** Parse date string using configured pattern */

165

public Date parse(String text, Locale locale) throws ParseException;

166

167

/** Format date using configured pattern */

168

public String print(Date value, Locale locale);

169

}

170

171

/**

172

* Annotation for specifying date format patterns

173

*/

174

@interface Formats.DateTime {

175

/** Date format pattern (e.g., "yyyy-MM-dd", "dd/MM/yyyy HH:mm") */

176

String pattern();

177

}

178

179

/**

180

* Annotation-driven date formatter using @DateTime configuration

181

*/

182

public class Formats.AnnotationDateFormatter extends Formatters.AnnotationFormatter<Formats.DateTime, Date> {

183

public Date parse(Formats.DateTime annotation, String text, Locale locale) throws ParseException;

184

public String print(Formats.DateTime annotation, Date value, Locale locale);

185

}

186

187

/**

188

* Annotation for non-empty string validation and formatting

189

*/

190

@interface Formats.NonEmpty {}

191

192

/**

193

* Formatter for @NonEmpty annotation handling

194

*/

195

public class Formats.AnnotationNonEmptyFormatter extends Formatters.AnnotationFormatter<Formats.NonEmpty, String> {

196

public String parse(Formats.NonEmpty annotation, String text, Locale locale) throws ParseException;

197

public String print(Formats.NonEmpty annotation, String value, Locale locale);

198

}

199

```

200

201

**Usage Examples:**

202

203

```java

204

import play.data.format.Formats.*;

205

206

public class EventForm {

207

@DateTime(pattern = "yyyy-MM-dd")

208

public Date startDate;

209

210

@DateTime(pattern = "yyyy-MM-dd HH:mm")

211

public Date endDateTime;

212

213

@NonEmpty

214

public String title;

215

216

public String description;

217

}

218

219

// The formatters will automatically handle conversion

220

public Result createEvent() {

221

Form<EventForm> form = Form.form(EventForm.class).bindFromRequest();

222

223

if (form.hasErrors()) {

224

return badRequest(form.errorsAsJson());

225

}

226

227

EventForm event = form.get();

228

// Dates are automatically parsed according to their patterns

229

return ok("Event created: " + event.title);

230

}

231

```

232

233

## Advanced Usage Patterns

234

235

### Custom Annotation Formatters

236

237

```java

238

// Custom annotation for phone number formatting

239

@Retention(RetentionPolicy.RUNTIME)

240

@Target(ElementType.FIELD)

241

public @interface PhoneNumber {

242

String region() default "US";

243

boolean international() default false;

244

}

245

246

// Custom annotation formatter

247

public class PhoneNumberFormatter extends Formatters.AnnotationFormatter<PhoneNumber, String> {

248

249

@Override

250

public String parse(PhoneNumber annotation, String text, Locale locale) throws ParseException {

251

// Parse and validate phone number based on region

252

PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();

253

try {

254

Phonenumber.PhoneNumber number = phoneUtil.parse(text, annotation.region());

255

if (!phoneUtil.isValidNumber(number)) {

256

throw new ParseException("Invalid phone number", 0);

257

}

258

return phoneUtil.format(number, PhoneNumberUtil.PhoneNumberFormat.E164);

259

} catch (NumberParseException e) {

260

throw new ParseException("Invalid phone number format", 0);

261

}

262

}

263

264

@Override

265

public String print(PhoneNumber annotation, String phoneNumber, Locale locale) {

266

PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();

267

try {

268

Phonenumber.PhoneNumber number = phoneUtil.parse(phoneNumber, null);

269

PhoneNumberUtil.PhoneNumberFormat format = annotation.international()

270

? PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL

271

: PhoneNumberUtil.PhoneNumberFormat.NATIONAL;

272

return phoneUtil.format(number, format);

273

} catch (NumberParseException e) {

274

return phoneNumber; // Return as-is if can't format

275

}

276

}

277

}

278

279

// Register the custom formatter

280

Formatters.register(String.class, new PhoneNumberFormatter());

281

282

// Usage in model

283

public class ContactForm {

284

@PhoneNumber(region = "US", international = false)

285

public String phoneNumber;

286

287

@PhoneNumber(region = "US", international = true)

288

public String internationalPhone;

289

}

290

```

291

292

### Locale-Specific Formatting

293

294

```java

295

// Locale-aware number formatter

296

public class LocalizedNumberFormatter extends SimpleFormatter<BigDecimal> {

297

298

@Override

299

public BigDecimal parse(String text, Locale locale) throws ParseException {

300

NumberFormat format = NumberFormat.getNumberInstance(locale);

301

Number number = format.parse(text);

302

return new BigDecimal(number.toString());

303

}

304

305

@Override

306

public String print(BigDecimal value, Locale locale) {

307

NumberFormat format = NumberFormat.getNumberInstance(locale);

308

return format.format(value);

309

}

310

}

311

312

// Usage with different locales

313

public Result processLocalizedForm() {

314

// Request locale affects formatting

315

Locale currentLocale = request().getLocale();

316

317

Form<ProductForm> form = Form.form(ProductForm.class).bindFromRequest();

318

// Numbers will be parsed according to current locale

319

320

return ok("Form processed with locale: " + currentLocale);

321

}

322

```

323

324

### Complex Type Formatting

325

326

```java

327

// Custom formatter for complex types

328

public class CoordinateFormatter extends SimpleFormatter<Coordinate> {

329

330

@Override

331

public Coordinate parse(String text, Locale locale) throws ParseException {

332

// Parse "lat,lng" format

333

String[] parts = text.split(",");

334

if (parts.length != 2) {

335

throw new ParseException("Invalid coordinate format", 0);

336

}

337

338

try {

339

double lat = Double.parseDouble(parts[0].trim());

340

double lng = Double.parseDouble(parts[1].trim());

341

return new Coordinate(lat, lng);

342

} catch (NumberFormatException e) {

343

throw new ParseException("Invalid coordinate values", 0);

344

}

345

}

346

347

@Override

348

public String print(Coordinate coord, Locale locale) {

349

return String.format("%.6f,%.6f", coord.getLatitude(), coord.getLongitude());

350

}

351

}

352

353

// Model using complex formatter

354

public class LocationForm {

355

@Required

356

public String name;

357

358

public Coordinate coordinates; // Uses CoordinateFormatter

359

360

public String description;

361

}

362

```

363

364

### Error Handling in Formatters

365

366

```java

367

public class SafeNumberFormatter extends SimpleFormatter<Integer> {

368

369

@Override

370

public Integer parse(String text, Locale locale) throws ParseException {

371

if (text == null || text.trim().isEmpty()) {

372

return null; // Allow empty values

373

}

374

375

try {

376

return Integer.parseInt(text.trim());

377

} catch (NumberFormatException e) {

378

throw new ParseException("Invalid number: " + text, 0);

379

}

380

}

381

382

@Override

383

public String print(Integer value, Locale locale) {

384

return value != null ? value.toString() : "";

385

}

386

}

387

388

// Formatter registration with error handling

389

public class FormatterConfig {

390

public static void registerCustomFormatters() {

391

try {

392

Formatters.register(Coordinate.class, new CoordinateFormatter());

393

Formatters.register(Integer.class, new SafeNumberFormatter());

394

} catch (Exception e) {

395

Logger.error("Failed to register custom formatters", e);

396

// Fallback to default formatters

397

}

398

}

399

}

400

```