or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

build-integration.mdimage-packaging.mdindex.mdjar-writing.mdlaunch-scripts.mdlayer-support.mdlayout-management.mdlibrary-management.mdmain-class-detection.mdrepackaging.md

jar-writing.mddocs/

0

# JAR Writing and Entry Management

1

2

Low-level utilities for writing JAR files with proper directory structure, duplicate handling, manifest generation, and nested library support. The JAR writing system provides the foundation for creating executable Spring Boot archives with embedded dependencies.

3

4

## Capabilities

5

6

### High-Level JAR Writing

7

8

Primary JAR writer class for creating executable archives with launch script support and automatic resource management.

9

10

```java { .api }

11

public class JarWriter implements AutoCloseable {

12

/**

13

* Create a JAR writer for the specified file.

14

*

15

* @param file the target JAR file to create

16

* @throws FileNotFoundException if the target file cannot be created

17

* @throws IOException if JAR writing initialization fails

18

*/

19

public JarWriter(File file) throws FileNotFoundException, IOException;

20

21

/**

22

* Create a JAR writer with launch script support.

23

* The launch script is prepended to the JAR to make it directly executable on Unix-like systems.

24

*

25

* @param file the target JAR file to create

26

* @param launchScript the launch script to prepend

27

* @throws FileNotFoundException if the target file cannot be created

28

* @throws IOException if JAR writing initialization fails

29

*/

30

public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundException, IOException;

31

32

/**

33

* Create a JAR writer with launch script and custom modification time.

34

*

35

* @param file the target JAR file to create

36

* @param launchScript the launch script to prepend

37

* @param lastModifiedTime the modification time to set on all entries

38

* @throws FileNotFoundException if the target file cannot be created

39

* @throws IOException if JAR writing initialization fails

40

*/

41

public JarWriter(File file, LaunchScript launchScript, FileTime lastModifiedTime) throws FileNotFoundException, IOException;

42

43

/**

44

* Close the JAR writer and finalize the archive.

45

* This method must be called to ensure the JAR is properly written.

46

*

47

* @throws IOException if closing fails

48

*/

49

@Override

50

public void close() throws IOException;

51

}

52

```

53

54

### Abstract JAR Writer

55

56

Base class providing comprehensive JAR writing functionality with extensibility points for custom implementations.

57

58

```java { .api }

59

public abstract class AbstractJarWriter implements LoaderClassesWriter {

60

/**

61

* Write the manifest file to the JAR.

62

* The manifest defines the main class, classpath, and other metadata.

63

*

64

* @param manifest the manifest to write

65

* @throws IOException if writing fails

66

*/

67

public void writeManifest(Manifest manifest) throws IOException;

68

69

/**

70

* Write an entry from an input stream.

71

*

72

* @param entryName the name/path of the entry within the JAR

73

* @param inputStream the input stream containing entry data

74

* @throws IOException if writing fails

75

*/

76

public void writeEntry(String entryName, InputStream inputStream) throws IOException;

77

78

/**

79

* Write an entry using a custom entry writer.

80

*

81

* @param entryName the name/path of the entry within the JAR

82

* @param entryWriter the writer for entry content

83

* @throws IOException if writing fails

84

*/

85

public void writeEntry(String entryName, EntryWriter entryWriter) throws IOException;

86

87

/**

88

* Write a nested library JAR to the specified location.

89

* Handles compression and ensures proper nested JAR structure.

90

*

91

* @param location the location within the JAR where the library should be placed

92

* @param library the library to write

93

* @throws IOException if writing fails

94

*/

95

public void writeNestedLibrary(String location, Library library) throws IOException;

96

97

/**

98

* Write an index file containing a list of entries.

99

* Used for classpath indexes, layer indexes, and other metadata files.

100

*

101

* @param location the location within the JAR for the index file

102

* @param lines the lines to write to the index file

103

* @throws IOException if writing fails

104

*/

105

public void writeIndexFile(String location, Collection<String> lines) throws IOException;

106

107

/**

108

* Write default Spring Boot loader classes to the JAR.

109

* These classes provide the runtime bootstrap functionality.

110

*

111

* @throws IOException if writing fails

112

*/

113

public void writeLoaderClasses() throws IOException;

114

115

/**

116

* Write specific loader implementation classes.

117

*

118

* @param loaderImplementation the loader implementation to use

119

* @throws IOException if writing fails

120

*/

121

public void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException;

122

123

/**

124

* Write loader classes from a specific JAR resource.

125

*

126

* @param loaderJarResourceName the name of the loader JAR resource

127

* @throws IOException if writing fails

128

*/

129

public void writeLoaderClasses(String loaderJarResourceName) throws IOException;

130

}

131

```

132

133

### Entry Writer Interface

134

135

Interface for writing custom entry content with optional size calculation.

136

137

```java { .api }

138

public interface EntryWriter {

139

/**

140

* Write entry content to the output stream.

141

*

142

* @param outputStream the output stream to write to

143

* @throws IOException if writing fails

144

*/

145

void write(OutputStream outputStream) throws IOException;

146

147

/**

148

* Get the size of the entry content.

149

* Returns -1 if size is unknown or variable.

150

*

151

* @return the entry size in bytes, or -1 if unknown

152

*/

153

default int size() {

154

return -1;

155

}

156

}

157

```

158

159

### Size Calculating Entry Writer

160

161

Entry writer implementation that calculates and tracks the size of written content.

162

163

```java { .api }

164

public class SizeCalculatingEntryWriter implements EntryWriter {

165

// Implementation calculates size during writing

166

// Useful for progress tracking and validation

167

}

168

```

169

170

### Loader Classes Writer

171

172

Interface for writing Spring Boot loader classes that provide runtime bootstrap functionality.

173

174

```java { .api }

175

public interface LoaderClassesWriter {

176

/**

177

* Write default loader classes to the archive.

178

*

179

* @throws IOException if writing fails

180

*/

181

void writeLoaderClasses() throws IOException;

182

183

/**

184

* Write specific loader implementation classes.

185

*

186

* @param loaderImplementation the loader implementation to write

187

* @throws IOException if writing fails

188

*/

189

void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException;

190

191

/**

192

* Write loader classes from a named JAR resource.

193

*

194

* @param loaderJarResourceName the loader JAR resource name

195

* @throws IOException if writing fails

196

*/

197

void writeLoaderClasses(String loaderJarResourceName) throws IOException;

198

199

/**

200

* Write a general entry to the archive.

201

*

202

* @param name the entry name

203

* @param inputStream the entry content

204

* @throws IOException if writing fails

205

*/

206

void writeEntry(String name, InputStream inputStream) throws IOException;

207

}

208

```

209

210

## Usage Examples

211

212

### Basic JAR Creation

213

214

```java

215

import org.springframework.boot.loader.tools.JarWriter;

216

import java.io.File;

217

import java.io.FileInputStream;

218

import java.io.IOException;

219

import java.util.jar.Manifest;

220

221

// Create a simple executable JAR

222

File targetJar = new File("output/myapp.jar");

223

try (JarWriter writer = new JarWriter(targetJar)) {

224

// Write manifest

225

Manifest manifest = new Manifest();

226

manifest.getMainAttributes().putValue("Manifest-Version", "1.0");

227

manifest.getMainAttributes().putValue("Main-Class",

228

"org.springframework.boot.loader.launch.JarLauncher");

229

manifest.getMainAttributes().putValue("Start-Class", "com.example.Application");

230

writer.writeManifest(manifest);

231

232

// Write application classes

233

File classesDir = new File("target/classes");

234

writeDirectory(writer, classesDir, "BOOT-INF/classes/");

235

236

// Write loader classes

237

writer.writeLoaderClasses();

238

}

239

240

// Helper method to write directory contents

241

private void writeDirectory(JarWriter writer, File dir, String prefix) throws IOException {

242

File[] files = dir.listFiles();

243

if (files != null) {

244

for (File file : files) {

245

if (file.isDirectory()) {

246

writeDirectory(writer, file, prefix + file.getName() + "/");

247

} else {

248

try (FileInputStream fis = new FileInputStream(file)) {

249

writer.writeEntry(prefix + file.getName(), fis);

250

}

251

}

252

}

253

}

254

}

255

```

256

257

### JAR with Launch Script

258

259

```java

260

import org.springframework.boot.loader.tools.*;

261

import java.io.File;

262

import java.util.Map;

263

264

// Create executable JAR with Unix launch script

265

File targetJar = new File("output/myapp.jar");

266

File scriptTemplate = new File("src/main/scripts/launch.sh");

267

268

// Configure launch script properties

269

Map<String, String> properties = Map.of(

270

"initInfoProvides", "myapp",

271

"initInfoShortDescription", "My Spring Boot Application",

272

"confFolder", "/etc/myapp"

273

);

274

275

LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);

276

277

try (JarWriter writer = new JarWriter(targetJar, launchScript)) {

278

// Write JAR contents

279

// ... (same as above)

280

}

281

282

// The resulting JAR can be executed directly:

283

// chmod +x myapp.jar

284

// ./myapp.jar

285

```

286

287

### Advanced Entry Writing

288

289

```java

290

import org.springframework.boot.loader.tools.*;

291

import java.io.File;

292

import java.io.ByteArrayOutputStream;

293

import java.io.IOException;

294

import java.nio.charset.StandardCharsets;

295

import java.util.Properties;

296

297

File targetJar = new File("output/myapp.jar");

298

try (JarWriter writer = new JarWriter(targetJar)) {

299

// Write custom properties file

300

Properties props = new Properties();

301

props.setProperty("app.version", "1.0.0");

302

props.setProperty("build.time", "2024-01-15T10:30:00Z");

303

304

writer.writeEntry("BOOT-INF/classes/application.properties", new EntryWriter() {

305

@Override

306

public void write(OutputStream outputStream) throws IOException {

307

props.store(outputStream, "Application Properties");

308

}

309

310

@Override

311

public int size() {

312

try {

313

ByteArrayOutputStream baos = new ByteArrayOutputStream();

314

props.store(baos, null);

315

return baos.size();

316

} catch (IOException e) {

317

return -1;

318

}

319

}

320

});

321

322

// Write text content

323

String configContent = "server.port=8080\nlogging.level.root=INFO";

324

writer.writeEntry("BOOT-INF/classes/config.properties", outputStream -> {

325

outputStream.write(configContent.getBytes(StandardCharsets.UTF_8));

326

});

327

}

328

```

329

330

### Library Integration

331

332

```java

333

import org.springframework.boot.loader.tools.*;

334

import java.io.File;

335

import java.util.List;

336

337

File targetJar = new File("output/myapp.jar");

338

try (JarWriter writer = new JarWriter(targetJar)) {

339

// Write manifest and classes

340

// ... (setup code)

341

342

// Write nested libraries

343

List<Library> libraries = List.of(

344

new Library(new File("lib/spring-boot-3.2.0.jar"), LibraryScope.COMPILE),

345

new Library(new File("lib/logback-classic-1.4.5.jar"), LibraryScope.RUNTIME)

346

);

347

348

Layout layout = new Layouts.Jar();

349

for (Library library : libraries) {

350

String location = layout.getLibraryLocation(library.getName(), library.getScope());

351

writer.writeNestedLibrary(location + library.getName(), library);

352

}

353

354

// Write classpath index

355

List<String> classpathEntries = libraries.stream()

356

.map(lib -> layout.getLibraryLocation(lib.getName(), lib.getScope()) + lib.getName())

357

.collect(Collectors.toList());

358

359

writer.writeIndexFile(layout.getClasspathIndexFileLocation(), classpathEntries);

360

}

361

```

362

363

### Custom AbstractJarWriter Implementation

364

365

```java

366

import org.springframework.boot.loader.tools.*;

367

import java.io.File;

368

import java.io.IOException;

369

import java.util.jar.JarOutputStream;

370

371

// Custom JAR writer with additional functionality

372

public class CustomJarWriter extends AbstractJarWriter {

373

private final JarOutputStream jarOutputStream;

374

375

public CustomJarWriter(File file) throws IOException {

376

// Initialize JAR output stream

377

this.jarOutputStream = new JarOutputStream(new FileOutputStream(file));

378

}

379

380

@Override

381

protected void writeToArchive(ZipEntry entry, EntryWriter entryWriter) throws IOException {

382

// Custom implementation for writing entries

383

jarOutputStream.putNextEntry(entry);

384

if (entryWriter != null) {

385

entryWriter.write(jarOutputStream);

386

}

387

jarOutputStream.closeEntry();

388

}

389

390

// Additional custom methods

391

public void writeCompressedEntry(String name, byte[] data) throws IOException {

392

ZipEntry entry = new ZipEntry(name);

393

entry.setMethod(ZipEntry.DEFLATED);

394

entry.setSize(data.length);

395

396

writeToArchive(entry, outputStream -> outputStream.write(data));

397

}

398

399

public void writeMetadata(String name, Object metadata) throws IOException {

400

// Custom metadata serialization

401

ByteArrayOutputStream baos = new ByteArrayOutputStream();

402

ObjectOutputStream oos = new ObjectOutputStream(baos);

403

oos.writeObject(metadata);

404

oos.close();

405

406

writeEntry(name, outputStream -> outputStream.write(baos.toByteArray()));

407

}

408

}

409

```

410

411

### Progress Tracking

412

413

```java

414

import org.springframework.boot.loader.tools.*;

415

import java.io.File;

416

import java.util.concurrent.atomic.AtomicLong;

417

418

// Track JAR writing progress

419

public class ProgressTrackingJarWriter extends AbstractJarWriter {

420

private final AtomicLong totalBytes = new AtomicLong(0);

421

private final AtomicLong writtenBytes = new AtomicLong(0);

422

private final ProgressCallback callback;

423

424

public ProgressTrackingJarWriter(File file, ProgressCallback callback) throws IOException {

425

super(/* initialization */);

426

this.callback = callback;

427

}

428

429

@Override

430

public void writeEntry(String entryName, EntryWriter entryWriter) throws IOException {

431

long entrySize = entryWriter.size();

432

if (entrySize > 0) {

433

totalBytes.addAndGet(entrySize);

434

}

435

436

// Wrap entry writer to track progress

437

EntryWriter trackingWriter = new EntryWriter() {

438

@Override

439

public void write(OutputStream outputStream) throws IOException {

440

OutputStream trackingStream = new OutputStream() {

441

@Override

442

public void write(int b) throws IOException {

443

outputStream.write(b);

444

writtenBytes.incrementAndGet();

445

updateProgress();

446

}

447

448

@Override

449

public void write(byte[] b, int off, int len) throws IOException {

450

outputStream.write(b, off, len);

451

writtenBytes.addAndGet(len);

452

updateProgress();

453

}

454

};

455

entryWriter.write(trackingStream);

456

}

457

458

@Override

459

public int size() {

460

return entryWriter.size();

461

}

462

};

463

464

super.writeEntry(entryName, trackingWriter);

465

}

466

467

private void updateProgress() {

468

long total = totalBytes.get();

469

long written = writtenBytes.get();

470

if (total > 0) {

471

double percentage = (double) written / total * 100;

472

callback.updateProgress(percentage, written, total);

473

}

474

}

475

476

@FunctionalInterface

477

public interface ProgressCallback {

478

void updateProgress(double percentage, long writtenBytes, long totalBytes);

479

}

480

}

481

```

482

483

### Integration with Repackager

484

485

```java

486

import org.springframework.boot.loader.tools.*;

487

import java.io.File;

488

489

// The repackager uses JAR writers internally

490

File sourceJar = new File("myapp.jar");

491

Repackager repackager = new Repackager(sourceJar);

492

493

// Configure repackager to use custom settings that affect JAR writing

494

repackager.setLayout(new Layouts.Jar());

495

repackager.setLoaderImplementation(LoaderImplementation.DEFAULT);

496

497

// The repackager will create its own JarWriter instance internally

498

repackager.repackage(Libraries.NONE);

499

500

// For more control, you can extend Repackager or use JarWriter directly

501

```

502

503

## JAR Structure

504

505

The JAR writing system creates archives with the following typical structure:

506

507

```

508

myapp.jar

509

├── META-INF/

510

│ └── MANIFEST.MF # JAR manifest with main class

511

├── BOOT-INF/

512

│ ├── classes/ # Application classes

513

│ │ └── com/example/...

514

│ ├── lib/ # Dependency libraries

515

│ │ ├── spring-boot-3.2.0.jar

516

│ │ └── logback-classic-1.4.5.jar

517

│ ├── classpath.idx # Classpath index (optional)

518

│ └── layers.idx # Layer index (optional)

519

└── org/springframework/boot/loader/ # Spring Boot loader classes

520

└── launch/...

521

```

522

523

This structure enables the Spring Boot loader to bootstrap the application and load dependencies at runtime.