OBJ vs STL: What Each Format Stores, and Why One Direction Costs More
Published 2026-08-23
Almost every comparison of these two formats leads with colour. STL cannot store it, OBJ can, therefore OBJ is the richer format. That is true, and it is also the least useful thing to know about them, because it only matters if your model was coloured in the first place.
The difference that reaches everyone is structural, and it sits one level below colour: STL does not share vertices and OBJ does. That single decision drives the file size, decides what a converter can and cannot preserve, and explains why converting an STL to OBJ hands you something that looks correct and falls apart the moment you try to edit it.
Start here: which one for your situation
| What you are doing | Use | Why |
|---|---|---|
| Printing a single colour part | STL | Universal support, and nothing else in the file for a slicer to use anyway |
| Printing in colour or multi material | 3MF | Not OBJ. Current slicers read colour from 3MF and ignore it in STL. See below |
| Sending a model to a print service | STL | It is what they ask for, and it cannot carry anything they would have to strip |
| Importing into a game engine | OBJ | STL has no UVs and no vertex normals, so it renders faceted and cannot be textured |
| Handing a model to a renderer | OBJ | Materials survive, provided the .mtl and images travel with it |
| Archiving something you will edit again | Neither | Keep the source file from the tool you modelled in. Both of these are export formats |
If your answer is in that table, the rest of this article is the reasoning behind it, plus the three failures that catch people during the conversion itself.
The same mesh, stored two ways
STL, designed for the first stereolithography machines in the late 1980s, describes a surface as an unstructured list of triangles. Each triangle carries its own normal vector and the absolute XYZ coordinates of its three corners. There is no index, no vertex list, and no record anywhere in the file that two adjacent triangles share an edge. The Wikipedia entry for the format documents the binary layout precisely: an 80 byte header, a 4 byte triangle count, then exactly 50 bytes per triangle.
OBJ, from Wavefront a few years later, works the way a programmer would expect. It declares a list of vertices at the top of the file, each on a v line, then defines faces on f lines that reference those vertices by index. Texture coordinates (vt) and normals (vn) get their own lists and their own indices. The Library of Congress format description covers the structure in detail.
A shared corner in an OBJ is one entry that several faces point at. The same corner in an STL is written out once for every triangle that touches it.
What that costs, measured
Published comparisons of these formats tend to assert file size relationships without showing the measurement, so we ran one. A UV sphere of 50 by 50 segments was generated in three.js r185, then written out by that library’s own OBJExporter and STLExporter. The mesh has 2,601 vertices and 4,900 triangles.
| Encoding | File size | Vertices written into the file |
|---|---|---|
| OBJ with UVs and normals | 625,486 bytes | 2,601 |
| OBJ, positions and faces only | 230,569 bytes | 2,601 |
| STL binary | 245,084 bytes | 14,700 |
| STL ASCII | 1,527,844 bytes | 14,700 |
The binary STL figure is not really a measurement at all. It is 84 plus 50 times 4,900, fixed by the format, and any correct exporter must produce exactly that number for this mesh.
The vertex column is the interesting one. The geometry never changed, but the STL contains 5.65 times as many vertex positions as the OBJ, because it repeats each shared corner for every triangle that uses it. In a closed mesh a typical vertex is surrounded by five or six triangles, which is where the factor comes from.
The size claim that is only half true
Now look back at the first two rows of that table, because they contradict something nearly every article on this subject says.
The usual claim is that STL files are larger than OBJ files, with vertex duplication given as the reason. Against an OBJ carrying only positions and faces, that holds: 245,084 against 230,569, a ratio of 1.06. But a real OBJ from a real modelling tool carries UV coordinates and vertex normals, and that version is 625,486 bytes. The STL is 0.39 times its size, comfortably smaller.
Both numbers describe the same sphere. The claim is conditional on what the OBJ contains, and the condition is almost never stated. If you are comparing a textured OBJ against its STL conversion, expect the file to get smaller, not larger, and expect that to be a symptom of everything the STL threw away rather than a sign of efficiency.
ASCII STL is the one case with no defence. At 1,527,844 bytes it is 6.2 times the binary version of the identical mesh, and it buys you only the ability to open it in a text editor. That multiplier is not fixed either, since it depends on how many characters each coordinate takes to print: the same comparison on the cube gave 2.2 times.
Converting OBJ to STL: what gets left behind
This direction is the common one, and it is lossy by design rather than by implementation. Nothing in the list below is a converter’s fault.
Materials and textures. The .mtl reference, the UV coordinates, and the image files all go. STL has nowhere to put them.
Vertex normals. OBJ stores a normal per vertex, which is what lets a renderer interpolate smooth shading across a curved surface. STL stores one normal per flat triangle. This is why an STL always renders faceted in a game engine no matter how dense the mesh is, and it cannot be recovered by recalculating normals afterwards, because the information about which edges were meant to be smooth was never written.
Quads and n-gons. OBJ faces can have any number of sides. STL is triangles only, so everything is triangulated on the way out. Any quad topology built for subdivision is gone.
Object structure. OBJ can name separate objects and groups. STL has no concept of an object, so a multi part assembly fuses into one continuous surface. Slicers cannot easily take it back apart, which is why both PrusaSlicer and Ultimaker Cura ship split-to-parts tools to deal with models that arrive this way.
In Blender, the export is File > Export > Stl (.stl). The dialog defaults to binary, and ticking Format: ASCII is what switches it. Two other options matter more than they look: Apply Modifiers, without which a subdivision surface exports at its unsubdivided cage, and Scene Unit, which applies the scene’s unit scale on the way out instead of leaving you to work out a scale factor by hand.
Converting STL to OBJ: why this direction is worse
Ask any AI assistant whether you can convert an STL back to OBJ and it will tell you yes, the geometry is preserved, you just do not get back what was never there. The first two thirds of that is right. The last third hides the problem.
At the file level the round trip really is clean. Every triangle in, every triangle out. What does not survive is the thing STL never recorded: which corners were the same corner.
The OBJ you get back has 14,700 vertices where the original had 2,601, and no two triangles are joined to each other. Visually it is identical, so nothing looks wrong. Then you try to work with it:
- Selecting an edge loop selects one edge, because there is no loop. Each triangle is an island.
- Smooth shading does nothing. The faceted look persists no matter what you set, because smoothing interpolates across shared vertices and there are none.
- A subdivision surface modifier inflates every triangle into its own rounded shard, and the mesh visibly explodes.
- UV unwrapping produces one island per triangle, splitting along every single edge.
- Rigging deforms each triangle independently and the surface tears open.
People hit this and conclude they broke something. The break happened at the conversion, and it happened in silence.
Welding it back
The repair is one operation, and it is not automatic. Blender’s STL importer has options for facet normals and mesh validation and nothing at all for merging coincident vertices, so the mesh arrives unwelded and stays that way until you say otherwise.
In Edit Mode, select everything, then Mesh > Merge > By Distance (shortcut M). It merges each cluster of vertices closer together than the Merge Distance threshold. Blender also offers a Weld Modifier that does the same non-destructively, which is the better choice if you want to keep the original point positions recoverable.
Two things about the threshold are worth knowing before you touch it:
Too small and nothing happens. Coordinates that were mathematically identical when exported are stored as 32 bit floats, so they usually come back bit for bit identical and the default threshold catches them. It is imported scan data and files that passed through several conversions where the points have drifted apart and need a larger value.
Too large and you create new problems. Raising the threshold until stubborn seams close will start merging points that were genuinely distinct, collapsing thin walls and small details into themselves. That produces exactly the topology that makes a slicer report non-manifold edges, and you have traded a cosmetic annoyance for a real defect. Raise it a little at a time and watch the vertex count.
If the mesh uses smooth shading with custom split normals, the merge dialog’s Sharp Edges option marks edges where they are needed so hard edges stay hard through the operation.
Colour: two conventions, and what 2026 software actually does
The STL specification has no colour field. What it does have is a two byte “attribute byte count” at the end of every 50 byte triangle record, normally set to zero, and two vendors independently decided to put colour in it.
The VisCAM and SolidView convention packs a 15 bit RGB value into those two bytes: bits 0 to 4 for blue, 5 to 9 for green, 10 to 14 for red, and bit 15 as a flag marking the colour valid.
The Materialise Magics convention writes a base colour into the 80 byte header as an ASCII COLOR= string, and uses the attribute bytes for per facet overrides. Its bit order is reversed relative to VisCAM, red at bits 0 to 4 and blue at 10 to 14, and its flag bit is inverted too.
The two are mutually incompatible, and nothing in the file says which one was used. A reader encountering coloured attribute bytes cannot tell whether it is looking at red or blue without being told.
That was a solvable problem twenty years ago when a handful of programs implemented one or the other. What settled it instead is that current slicers stopped participating:
| Software | Reads STL colour? |
|---|---|
| PrusaSlicer | No. Colour for multi material comes from separate part meshes or its own in-slicer painting tools |
| Bambu Studio | No, explicitly. Bambu Lab’s forum tells users to bring colour in as 3MF or OBJ; a coloured STL imports as one uniform mass |
| Ultimaker Cura | No. Its own project format carries multi material assignments instead |
| Blender | No. A binary STL with colour bytes imports as plain grey geometry |
| Microsoft 3D Builder | Yes, it reads and writes coloured STL, though which of the two conventions it defaults to on export is not publicly documented |
So the practical answer has become simple, even though the technical answer is messy. In any current workflow, treat STL as a format that cannot carry colour. If colour has to survive to the printer, use 3MF, which is what the slicers above have standardised on.
The three failures that actually catch people
Community threads about these formats cluster around the same three problems, and none of them is about colour or topology.
Everything is 1000 times the wrong size
Neither format records a unit. A vertex at 60 is at sixty of something, and the file does not say what.
Modelling and DCC applications generally treat one unit as one metre. Slicers treat it as one millimetre. Export a 60mm part from a tool working in metres and the slicer receives a 0.06mm speck; go the other way and a model overshoots the build plate by three orders of magnitude.
Fix it at export rather than by rescaling in the slicer: set the export scale factor, or tick Scene Unit in Blender so the scene’s own unit settings are applied. Our GLB to STL converter prints the output dimensions in millimetres next to the source bounding box for exactly this reason, because the mistake is invisible until the model is somewhere else.
The .mtl did not come along
An OBJ contains no materials. It names an .mtl file, which in turn names image files, so a textured OBJ is always at least three files pretending to be one.
Three ways that breaks, all common:
- Only the .obj gets uploaded. Browser based converters silently drop the material mapping when the referenced
.mtlis not in the payload, and the output arrives with no colour at all. - The .mtl recorded an absolute path. It points at a directory on the machine that exported it, which does not exist on yours. In Blender’s OBJ export dialog, the Path Mode setting controls this, and Copy is the portable choice: it copies the images next to the exported file and references them relatively.
- A texture filename contains a space. Some parsers fail on it, sometimes silently. Underscores cost nothing.
The model prints inside out
Boolean operations and mirrored geometry can leave triangles wound the wrong way round, which flips their normals. A slicer reading a flipped normal concludes the solid is on the other side of that surface and treats the region as a void, so parts of the model come out hollow or missing.
Both formats record winding, and both will faithfully carry the error across a conversion. In Blender the fix is Shift + N in Edit Mode with everything selected, which recalculates normals to face outward. If the slicer is still unhappy afterwards, the problem is more likely holes or non-manifold geometry than winding.
The short version
OBJ and STL are both export formats from a time before either had competition, and both are still here because everything reads them.
Choose by direction of travel. Going to a printer, STL is the safe answer and gives up nothing the slicer would have used, unless colour is involved, in which case the answer is 3MF. Going to a renderer or an engine, OBJ preserves the UVs, normals and materials that make a model usable, as long as you keep its companion files together.
Going backwards from STL, expect to weld the mesh before editing it, and expect the materials to stay gone. The triangles come back. The connections do not.
You can check any of this on a file you have without installing anything: our STL viewer and OBJ viewer both report triangle counts in the browser, and the format compatibility table lists which tools in the directory read and write each one.
Frequently asked questions
Should I use ASCII or binary STL?
Binary, unless something specifically needs to read the file as text. Binary STL spends a fixed 50 bytes per triangle. ASCII writes the same triangle as several lines of decimal numbers, and how much larger that runs depends entirely on how long those numbers are. Measured on three meshes, ASCII came out 2.2 times larger for a cube and 6.2 times larger for a sphere. Both encodings describe identical geometry and every mainstream slicer reads both, so the text version buys nothing but size.
Can I keep textures if I convert an OBJ to STL?
No. STL stores triangles and nothing else, with no structure to hold UV coordinates or reference an image. Baking the texture into the geometry is not a workaround either, because STL colour extensions apply per triangle at best, so reproducing a photographic texture would need the mesh subdivided to roughly one triangle per pixel. If the colour has to survive, the format to convert to is 3MF for printing or GLB for the web, not STL.
Why does my OBJ load with no materials in a game engine or renderer?
An OBJ does not contain its own materials. It names a companion .mtl file, and that file names the image textures, so all three parts have to travel together. The three common breakages are uploading the .obj alone to a converter, an .mtl that recorded an absolute path from the machine that exported it, and spaces in a texture filename. Blender's export dialog has a Path Mode setting, and choosing Copy bundles the images next to the file with relative paths.
Why is my model tiny or enormous after converting?
Neither format records a unit. A number in the file is just a number, and each program decides what it means. Modelling and DCC tools generally treat one unit as one metre while slicers treat it as one millimetre, which is where the factor of 1000 comes from. It is a display convention on both sides rather than a fault in the file, so the fix is to set the export scale, or to use Blender's Scene Unit option, rather than to look for a corrupted mesh.
Do I have to merge vertices after importing an STL?
If you plan to edit the mesh, yes. STL writes every triangle's three corners independently, so nothing in the file records that two triangles share an edge, and Blender's STL importer offers no option to reconnect them. Until you run Mesh, Merge, By Distance in Edit Mode, edge loops will not select, smooth shading stays faceted, and subdivision or rigging will tear the mesh apart. If you are only viewing or printing the file, none of that matters and you can skip it.